Archive: UAC plug-in and MultiUser dialog


UAC plug-in and MultiUser dialog
Hi,
I'm trying to use UAC plug-in in my script which already uses the MultiUser dialog for selection between all-users and current-user installation. Here it comes the problem. For UAC plug-in I have to set the user level to user, but then I get an error from MultiUser macro, because it needs admin rights. But I need both things in my script. Can you advice me how to solve my problem? I don't want to make my own MultiUser page, because I'm not very familiar with this and it will take time until I make it. Is there an easy solution?


If you want to use the UAC plugin, you will have to use a custom dialog. The design of MultiUser is broken IMHO, if you set requestexecutionlevel to admin, you can't do a "single user" install


Tanks Anders,
I expected that this will be the answer. However, is there anybody who can share some code for such a custom multi-user dialog?


Use nsDialogs to create a custom page.

NSIS\Examples\nsDialogs\example.nsi
NSIS\Docs\nsDialogs\Readme.html

Edit: Here, I'll share some code that I've been writing for my elevate-only-when-necessary installer:

Function MODESELECT
nsDialogs::Create /NOUNLOAD 1018
Pop $2
${NSD_CreateGroupBox} 0u 40u 300u 92u "Installation User Policy"
Pop $2
${If} $ACCESSLEVEL == "User"
${NSD_CreateLabel} 10u 53u 280u 25u "Setup can install this software \
either for this user, or for all users on this \
computer. You will require Administrator privileges \
to install this software for all users. Select the \
required option below and click Next to continue."
${Else}
${NSD_CreateLabel} 10u 53u 280u 25u "Setup can install this software \
either for this user, or for all users on this \
computer. Select the required option below and \
click Next to continue."
${EndIf}
Pop $2
System::Call "advapi32::GetUserName(t.r2, *i ${NSIS_MAX_STRLEN})"
${NSD_CreateRadioButton} 10u 83u 280u 14u "Install only for this &user ($2)"
Pop $NSDFIELD1
${NSD_OnClick} $NSDFIELD1 MODESELECTRadioButton
${NSD_CreateRadioButton} 10u 97u 280u 14u "Install for &all users"
Pop $NSDFIELD2
${NSD_OnClick} $NSDFIELD2 MODESELECTRadioButton
${If} $ACCESSLEVEL == "User"
SendMessage $NSDFIELD1 ${BM_SETCHECK} 1 0
${NSD_SetFocus} $NSDFIELD1
${Else}
SendMessage $NSDFIELD2 ${BM_SETCHECK} 1 0
${NSD_SetFocus} $NSDFIELD2
${EndIf}
call MODESELECTRadioButton
!insertmacro MUI_HEADER_TEXT "Select Installation Type" \
"Choose how you want to install $(^Name)."
nsDialogs::Show
FunctionEnd

Function MODESELECTRadioButton
${If} $ACCESSLEVEL == "User"
${NSD_GetState} $NSDFIELD2 $2
GetDlgItem $0 $hwndParent 1
SendMessage $0 /*BCM_SETSHIELD*/0x0000160C 0 $2
${EndIf}
FunctionEnd

Function MODESELECTLeave
SendMessage $NSDFIELD2 ${BM_GETCHECK} 0 0 $ALLUSERS
${If} $ALLUSERS == 1
${AndIf} $ACCESSLEVEL == "User"
;Call for elevation.
StrCpy $ELEVATIONPAGE "MODESELECT"
${SelfElevation}
${EndIf}
FunctionEnd


Please don't copy paste this code, as it hasn't even been tested to compile yet. Also it is closely tied in to a shitload of other code, that I'm not going to describe here. But it may serve to give you an idea of how to go about doing this your own way.

Thanks MSG! I will try to understand it and use it :)


Hi MSG,
Now I made my dialog. The next step is to implement the UAC plug-in. In connection to this I have two questions:
1. You make the elevation when leaving the multi-user dialog. Is it possible? I thought that this must be done in .onInit function before creation of the first dialog.
2. You make the elevation only when installation for all users is selected and the current level is user. I think that there will be a problem to install something that tries to install DLLs in Windows/System32 if you select current user installation and the current level is user. What do you think? Isn't it necessary to make elevation always if the current level is user?
I hope that I got the correct idea what the UAC is doing, but I really need some more help. I read some treads, connected to his problem, but some pieces of information are still missing :(.


Originally posted by TrifonovS
1. You make the elevation when leaving the multi-user dialog. Is it possible? I thought that this must be done in .onInit function before creation of the first dialog.
I knew you were going to ask that. :-) Yes, it is possible but rather complicated. Look at UAC_DualMode.nsi in the experimental build. Here's what I use:

(Oops, let me pastebin that...)
http://nsis.pastebin.com/f14fb0a6a
Please note that all the UAC magic is taken from UAC_DualMode as designed by Anders. I wouldn't be able to come up with all this stuff on my own, I'm sure.

Originally posted by TrifonovS
2. You make the elevation only when installation for all users is selected and the current level is user. I think that there will be a problem to install something that tries to install DLLs in Windows/System32 if you select current user installation and the current level is user. What do you think? Isn't it necessary to make elevation always if the current level is user?
Yes, if you need to do anything admin-ish, then you might as well elevate in .onInit and save yourself all the trouble. The point of my whole exercise is to have an installer that supports user-level installation, for those applications that don't specifically *must* be installed as admin. So what I do is elevate only when necessary, for example when trying to install into a protected directory, or trying to install for all users.

Using the above idea, the installer will only ask for elevation after the user selects some admin-ish option. The installer is elevated, all important variables that may have already been given values ($INSTDIR etc) are synchronized into the new admin-instance, I use the $ELEVATIONPAGE variable to jump to the same page they just elevated from... And voila, all the user notices is a popup asking for the admin password.


Of course, I had to add piles of peripheral code to support win9x, power users on win2k/XP, detection of whether a directory is writable by the user, disable the page's Back button after elevation, and so on and so forth. But that's all in a day's work. :)

MSG, thanks a lot for the huge help. I got the idea. Actually I have one more question (for now) :) ... Now I'm thinking about how to jump to the page using the information from the variable $ELEVATIONPAGE. Is there an easy way, or I have to go trough all pages and skip them until I reach the needed one?


http://nsis.pastebin.com/ff7389e9

Of course, the jumps are relative so you'll have to change "3", "4", "5" and "6" to the proper values. And you'll have to disable the Back button after some (or all) jumps, for example to prevent someone from doing "Install for all users"->Next->(Elevate)->Back->"Install for this user (Administrator)".


Oops, one more question. In the example UAC_DualMode.nsi the plug-in is not unloaded. Is it correct? In the repository is described that the following calls must be included in the code:

Function .OnInstFailed
UAC::Unload ;Must call unload!
FunctionEnd

Function .OnInstSuccess
UAC::Unload ;Must call unload!
FunctionEnd

When I put this to my code it can not be compiled. Can you tell me what I'm doing wrong?


The latest versions of NSIS use a new plugin model: plugins no longer require manual unloading. UAC applies this new plugin model.


Hi again,
One more question. How can I check the current level? I want to know if the account from where the installer is called has admin level or not. Simply said I need to know how to initialize the mentioned above variable ACCESSLEVEL...


userinfo plugin.