alex_sh
14th February 2010 13:42 UTC
Determine if Admin rights are needed (UAC)
Hello all,
I have an installer which can be used for "global", as well as "local" installations. By "global" I mean installing to system locations, creating shortcuts, etc...
While "local" installation is essentially just extracting into a directory (INSTDIR).
The thing is, I don't know how to determine if it needs administrative privileges when doing a "local" installation. For example, doing an installation to "C:\program files\..." would require admin privileges, while installing to a user's personal directory wouldn't.
Right now I just have "RequestExecutionLevel admin", but it doesn't allow ordinary users to do a "local" install to their directories.
I know there's a UAC plugin ( http://nsis.sourceforge.net/UAC_plug-in ), but there's almost no documentation there and I have no idea how to use it to achieve my goal.
Any help would be really appreciated.
Thanks in advance!
redxii
14th February 2010 15:51 UTC
You looking for something like this? http://nsis.sourceforge.net/Docs/MultiUser/Readme.html
MSG
14th February 2010 15:58 UTC
Here's what I use, based on a trick shown to me by Anders:
Function TestFileAccess
;ACCESS_MASK = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE
; = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE |
; STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA |
; STANDARD_RIGHTS_EXECUTE | FILE_EXECUTE | DELETE
System::Call "kernel32::GetFileAttributes(t `$INSTDIR`)i .r1"
IntOp $1 $1 & 0x0010
${If} $1 != 0
${AndIf} ${FileExists} "$INSTDIR"
;Instdir exists and is a directory.
; Check instdir for 0x1301BB (with DELETE)
; We need delete privs: Instdir may have been created by another user, thus not giving us delete access.
System::Call "kernel32::CreateFile(t `$INSTDIR`,i 0x1301BB,i 3,i 0,i 3,i 0x02000000,i 0)i .r1"
System::Call "kernel32::CloseHandle(ir1)"
${Else}
;Instdir either doesn't exist, or doesn't exist as a directory yet.
;Search for an instdir\..\..\.. directory that actually exists
push $2
push $3
${RemoveTrailingBackslashSpaces} $2 $INSTDIR
${Do}
System::Call "kernel32::GetFileAttributes(t `$2`)i .r1"
IntOp $1 $1 & 0x0010
${If} $1 != 0
${AndIf} ${FileExists} "$2"
;It exists and is a directory.
${ExitDo}
${EndIf}
${CutBeforeLastBackslash} $2 $2
StrLen $3 $2
${If} $3 == 2
${ExitDo}
${EndIf}
${Loop}
; Check instdir\..\..\.. for 0x1201BB (without DELETE).
; We don't need delete privs: The user creating a dir is always owner, so as long as we
; create instdir at user level we'll get delete access automatically.
System::Call "kernel32::CreateFile(t `$2`,i 0x1201BB,i 3,i 0,i 3,i 0x02000000,i 0)i .r1"
System::Call "kernel32::CloseHandle(ir1)"
pop $3
pop $2
${EndIf}
FunctionEnd
Function YourFunction
Call TestFileAccess
${If} $1 == -1
;We don't have access rights. Pop an error.
${EndIf}
FunctionEnd
The point of interest is the CreateFile/CloseHandle command. It does not actually create anything, but it will return a positive handle ID if the user calling it has enough privileges. If not, it'll return -1. Oh, and I pasted those two macros I used in here:
http://nsis.pastebin.com/fc93d069
alex_sh
14th February 2010 16:38 UTC
Thank you both for your replies.
The MultiUser stuff - I think I read somewhere that it's not compatible with UAC. Also, I don't quite see the thing I'm looking for there (maybe I'm looking at it the wrong way).
MSG, thanks a lot for your code.
One question though - is this code known to work with WOW emulation on 64-bit windows? I know windows does a lot of behind-the-scenes path substitution in that mode, and e.g. "C:\windows\system32"'s parent directory may not always be "C:\windows". (I wrote that as an example - it might work for these directories, but for others it might not).
MSG
14th February 2010 17:41 UTC
One question though - is this code known to work with WOW emulation on 64-bit windows? I know windows does a lot of behind-the-scenes path substitution in that mode, and e.g. "C:\windows\system32"'s parent directory may not always be "C:\windows". (I wrote that as an example - it might work for these directories, but for others it might not).
Hmm... That's an interesting question. I was going to say that, because the redirection happens behind the scenes, the above code should not be affected by it. "$INSTDIR\..\.." could never become some strange virtualstore directory, because $INSTDIR is just a string, and that string does not contain virtualstore.
But then I realized I have no idea when exactly the path gets expanded to its physical location. If "$PROGRAMFILES\Test\.." is expanded to $PROGRAMFILES before it's sent to win32 api, there's no problem. But if it's the api commands that expand the path... It might get tricky.
Then again, although I don't know how to find out which of the two actually happens, I haven't encountered any problems with the code. But perhaps some programmer could help out to clarify?
Anders
14th February 2010 20:03 UTC
x64.nsh has functions to turn redirection on and off
MSG
15th February 2010 04:51 UTC
Oh, right, he was asking about WOW64. What about permissions-related virtualization, though?
alex_sh
19th February 2010 09:57 UTC
There seem to be a lot of cases when windows decides to rewrite the paths. I think this breaks the parent directory / subdirectory relationships based on path string parsing.
There's this: http://support.microsoft.com/kb/927387
And there's also WOW64. For WOW64 I think the most correct way would be to get a real path from our virtualized path, and then use it in the above checks. Simply disabling the path translation is incorrect IMHO.
I found the question asking about how to do this ( http://stackoverflow.com/questions/942110/translating-32-bit-paths-to-their-wow64-equivalents ), but it remains unanswered.