Archive: Check if directory is writable


Check if directory is writable
Hi,

I'm working on a installer that needs to install programs on non administrative accounts, and has to work on Vista with UAC on or off. So i made a script that does not require admin rights and by default it installs a program to C:\My Games, so far so good. The scrip works just fine until user decides to change installDir, if that happened and the installdir is a Directory that requires admin rights to write the files will not copy :( So i i had an idea to check if selected directory can be written this is the code for that

!include "FileFunc.nsh"
!insertmacro GetFileAttributes
!insertmacro GetParent

Function IsWritable
!define IsWritable `!insertmacro IsWritableCall`

!macro IsWritableCall _PATH _RESULT
Push `${_PATH}`
Call IsWritable
Pop ${_RESULT}
!macroend

Exch $R0
Push $R1

loop:
StrLen $R1 $R0
StrCmp $R1 0 isbad
${GetFileAttributes} $R0 "DIRECTORY" $R1
StrCmp $R1 0 parent
${GetFileAttributes} $R0 "READONLY" $R1
StrCmp $R1 1 isbad
Goto isok

parent:
${GetParent} $R0 $R0
Goto loop

isbad:
StrCpy $R1 1
Goto exit

isok:
StrCpy $R1 0
Goto exit

exit:
Exch
Pop $R0
Exch $R1

FunctionEnd

Function .onVerifyInstDir

Push $R1
${IsWritable} $INSTDIR $R1
IntCmp $R1 0 pathgood
Pop $R1
Abort

pathgood:
Pop $R1

FunctionEnd

I based IsWritable function on some code i found here, the problem is that this does not work as intended, for eg if i select ProgramFiles it will detect that i cant write there, but if i select any other dir inside ProgamFiles it will allow me to install there. So i had another idea to check if any parent dir of the InstalDir is read-only, that did not work as well because it allowed installation to C:\Windows. My next idea is to find first parent dir of the install dir and create a dir there, if dir is created i will allow installation in that dir and than delete that dir, if dir was not created i will not allow to install there. I know that it is not the best idea but I cant find any other solution to my problem. Does anyone know how to check if installdir can be written ? Or is there any other way to prevent user to select invalid installdir ?


I wouldn't suggest relying on file attributes when dealing with the UAC. I think it's better to try to create the directory (and possibly a file in it, in case the directory already exists), and consider the directory invalid if the installer couldn't create the directory (and/or the file)


Creating a directory, did not work :(

CreateDirectory "$R0\T_S"
${GetFileAttributes} "$R0\T_S" "DIRECTORY" $R1
StrCmp $R1 0 isbad
RMDir "$R0\T_S"

$R0 contains the path to the firs existing parent of the installdir, CreateDirectory - i have read that this function sets an error if it fails, how can i check if the function failed ?


Use IfErrors.


Hi, again i have spend quite some time trying to make this work. Does anyone know what can i use to create a installer that will properly work on vista ? By properly i mean handling user directory with UAC, creating shortcuts etc. The current version of installation can be downloaded @ http://codeminion.com/downloads/StoneLoopsSetup.exe
it works correctly- creates shortcuts in proper places, does not require admin password, but when user who is not admin changes installdir to one that requires admin rights the installer will not copy the files. Is there anyone out there who know how to handle this ?


Try something more like this:
CreateDirectory $INSTDIR
IfErrors isbad
RMDir $INSTDIR

Alternatively, reject the installation directory as invalid if traversing the list of parents finds a match for $PROGRAMFILES, $WINDIR, etc., even if the user is operating in XP or as an admin with the UAC off.


Hey,

mauvecloud, thx for your help, i finally got the script working, here is the code: i will improve it a bit to make it more better but hey it works!

RequestExecutionLevel user

!include "FileFunc.nsh"
!insertmacro GetFileAttributes
!insertmacro GetParent

Function IsWritable

SetShellVarContext current
!define IsWritable `!insertmacro IsWritableCall`

!macro IsWritableCall _PATH _RESULT
Push `${_PATH}`
Call IsWritable
Pop ${_RESULT}
!macroend

Exch $R0
Push $R1

loop:
StrLen $R1 "$R0"
StrCmp $R1 0 isbad

IfFileExists "$R0" 0 parent
${GetFileAttributes} "$R0" "DIRECTORY" $R1
StrCmp $R1 0 parent
${GetFileAttributes} "$R0" "READONLY" $R1
StrCmp $R1 1 isbad
Goto testdir

parent:
${GetParent} "$R0" "$R0"
Goto loop

isbad:
StrCpy $R1 1
Goto exit

testdir:
ClearErrors
CreateDirectory "$R0\T_S"
IfErrors isbad isok

isok:
RMDir "$R0\T_S"
StrCpy $R1 0
Goto exit

exit:
Exch
Pop $R0
Exch $R1

FunctionEnd

Function .onVerifyInstDir

Push $R1
${IsWritable} $INSTDIR $R1
IntCmp $R1 0 pathgood
Pop $R1
Abort

pathgood:
Pop $R1

FunctionEnd

If you have any ideas no how to improve this let me know


Hi again,

I have improved this function and now it looks like this:

Function .onVerifyInstDir

Push $R0
Push $R1
Push $R2

StrCpy $R0 "$INSTDIR"
StrCpy $R2 "some_unique_name"

loop:
StrLen $R1 "$R0"
StrCmp $R1 0 pathbad
IfFileExists "$R0" 0 parentLoop
${GetFileAttributes} "$R0" "DIRECTORY" $R1
StrCmp $R1 0 parentLoop
${GetFileAttributes} "$R0" "READONLY" $R1
StrCmp $R1 1 pathbad
ClearErrors
IfFileExists "$R0\$R2" pathgood 0
CreateDirectory "$R0\$R2"
IfErrors pathbad 0
RMDir "$R0\$R2"
Goto pathgood

parentLoop:
${GetParent} "$R0" "$R0"
Goto loop

pathbad:
Abort

pathgood:

Pop $R2
Pop $R1
Pop $R0

FunctionEnd