madjerry
4th April 2008 12:48 UTC
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 ?
mauvecloud
4th April 2008 13:05 UTC
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)
madjerry
4th April 2008 14:46 UTC
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 ?
mauvecloud
4th April 2008 14:53 UTC
Use IfErrors.
madjerry
4th April 2008 15:00 UTC
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 ?
mauvecloud
4th April 2008 15:21 UTC
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.
madjerry
4th April 2008 15:30 UTC
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
madjerry
4th April 2008 16:20 UTC
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