You are here

RMDirIfNotJunction - Removing empty directories but checking if they are junctions first

9 posts / 0 new
Last post
John T. Haller
John T. Haller's picture
Offline
Last seen: 2 hours 19 min ago
AdminDeveloperModeratorTranslator
Joined: 2005-11-28 22:21
RMDirIfNotJunction - Removing empty directories but checking if they are junctions first

I'm testing an NSIS function that will remove an empty directory after checking if it is a junction. Essentially, it's an RMDir (without the /r) but checking to see if the directory referenced is a REPARSE_POINT first.

Why do this? Some users use junction points within APPDATA to redirect specific directories to other drives for their local apps. For apps like Firefox and Thunderbird, the best way to do this is to use the built-in profile manager to point to a profile on your secondary drive. Some users prefer to use junctions, though. Personally, I avoid junctions and recommend against them full stop. Junctions on Windows are incredibly buggy and probably shouldn't be used.

What happens? As NSIS uses standard Windows calls, it checks to see if a given directory is empty before removing directory for commands like RMDir. Unfortunately, due to junctions being junctions, they report as empty even though the directory they point to has things in it because the directory referenced doesn't have anything in it because it doesn't exist. aka... buggy.

Working around it. I've put together a quick function that is a drop in replacement for RMDir that will remove the directory buy only after it is checked to see if it is a junction. The eventual goal is to consider replacing RMDir calls within launchers to check for the niche users that intersperse junctions within APPDATA.

Here's the code:

; RMDirIfNotJunction 1.1 (2020-04-29)
;
; Removes an empty directory if it is not a junction
;
; Usage: ${RMDirIfNotJunction} REMOVE_PATH

!include "FileFunc.nsh"

Function RMDirIfNotJunction
	;Start with a clean slate
	ClearErrors
	
	;Get our parameters
	Exch $0 ;REMOVE_PATH
	Push $1 ;TempVar
	
	;Determine if it is a junction
	${GetFileAttributes} "$0" "REPARSE_POINT" $1
	
	${If} $1 == 0
		;Not a junction, remove the directory if empty
		RMDir $0
	${EndIf}
	
	;Clear the stack
	Pop $1
	Pop $0
FunctionEnd

!macro RMDirIfNotJunction REMOVE_PATH
  Push `${REMOVE_PATH}`
  Call RMDirIfNotJunction
!macroend

!define RMDirIfNotJunction '!insertmacro "RMDirIfNotJunction"'

And here are a couple test launchers for FirefoxPortable.exe and ThunderbirdPortable.exe. Drop them in place of the standard launcher.

Be sure to backup your Firefox/Thunderbird Portable profile *AND* your local Firefox/Thunderbird data before testing just to be safe.

Please report back your results.

3D1T0R
3D1T0R's picture
Offline
Last seen: 1 year 1 month ago
Developer
Joined: 2006-12-29 23:48
Why not [/r] [/REBOOTOK] ?

Is there a reason /R & /REBOOTOK are apparently unsupported by this?
I suppose you might only be looking at it right now for Mozilla apps, but it might be nice to also use this in PAL too, and I believe there are several places where RMDir /R gets called that might benefit from becoming ${RMDirIfNotJunction} /R instead.
Not to mention that it may also benefit other people who might want /R and/or /REBOOTOK functionality too.

So basically, I'm just wondering if there's a reason not to? or if it's just because you haven't written it to do that? (yet?)

P.S.: I'll try testing it tomorrow if I can.

~3D1T0R

John T. Haller
John T. Haller's picture
Offline
Last seen: 2 hours 19 min ago
AdminDeveloperModeratorTranslator
Joined: 2005-11-28 22:21
One Purpose

Basically REBOOTOK serves no purpose for portable apps. And RMDir /r means remove it no matter what. The sole point of this is to enable a safe removal of an empty directory but ensure it's not a junction. I may rename it to RMDirIfEmptyAndNotJunction. I may not implement this at all due to the bugginess of junctions in the first place.

Sometimes, the impossible can become possible, if you're awesome!

3D1T0R
3D1T0R's picture
Offline
Last seen: 1 year 1 month ago
Developer
Joined: 2006-12-29 23:48
Unless people say it's causing problems, I'd put it in.

I understand that /REBOOTOK serves little to no purpose for PA.c (but might help others who want to delete something if it's not a junction, but don't care if it doesn't go away till after a reboot), but I don't see why you think ${RMDirIfNotJunction} /r $SomeDirectory would never be useful.

If you don't feel it's worth the time to implement it, that's one thing, but the way you say it, it sounds like you're claiming "nobody would ever use that". Why is that?

~3D1T0R

3D1T0R
3D1T0R's picture
Offline
Last seen: 1 year 1 month ago
Developer
Joined: 2006-12-29 23:48
Test results: Win7x86-64=☑

This works beautifully for me.

I don't usually use Symbolic Links, Junctions, etc. in Windows, as they're treated (by Microsoft) like 'second class citizens' in that they seem to have no interest in fixing bugs such as the one you've mentioned (I do on the other hand use them on Linux, where if such a bug were to appear, it would likely get noticed and fixed very quickly), but having tested this with reparse points created via mklink /D, mklink /J, and by mounting another volume in the %AppData%\Mozilla folder, I can verify that the launchers posted work quite nicely on Windows 7, with no hiccups caused by the reparse points, where the current release's launcher improperly removes all of these. I can also verify that with normal folders (not reparse points) these launchers properly delete the folders if empty, and they do not delete them if they are not empty (again, this is on Windows 7).

~3D1T0R

John T. Haller
John T. Haller's picture
Offline
Last seen: 2 hours 19 min ago
AdminDeveloperModeratorTranslator
Joined: 2005-11-28 22:21
Updated to 1.1

I've update the function to ensure the necessary include is made and updated the test launchers to the current Firefox and Thunderbird versions.

Sometimes, the impossible can become possible, if you're awesome!

John T. Haller
John T. Haller's picture
Offline
Last seen: 2 hours 19 min ago
AdminDeveloperModeratorTranslator
Joined: 2005-11-28 22:21
Included In Firefox Releases

This is included in Firefox Portable 76.0, Beta/Dev 77.0, Nightly 78.0, and ESR 68.8.0 and later. FirefoxPortable.exe launcher version 2.2.1 and later.

Sometimes, the impossible can become possible, if you're awesome!

Gord Caswell
Gord Caswell's picture
Offline
Last seen: 1 hour 40 min ago
DeveloperModerator
Joined: 2008-07-24 18:46
PAL Integration?

I've just done a quick check of PAL, and there are currently 12 calls to RMDir within PAL, 9 of which call it as RMDir /r (or /R, interestingly).
Provided this is usable as a drop-in for RMDir /r, and I believe you've indicated above that it is, I'm comfortable adding this functionality to PAL.

John T. Haller
John T. Haller's picture
Offline
Last seen: 2 hours 19 min ago
AdminDeveloperModeratorTranslator
Joined: 2005-11-28 22:21
Remove If Empty

I'm thinking it's most useful for places where we remove a directory if empty via the launcher ini. For things we backup and restore, we'd need to come up with something else I think.

Sometimes, the impossible can become possible, if you're awesome!

Log in or register to post comments