仅在需要时才使Inno Setup安装程序请求权限提升

Inno Setup安装程序具有PrivilegesRequired指令 ,可用于控制安装程序启动时是否需要特权提升。 我希望我的安装程序能够为非pipe理员用户工作(将我的应用程序安装到用户文件夹而不是Program Files没有问题)。 所以我把PrivilegesRequired设置为none (无证的值)。 这使UAC提示popup只有pipe理员用户,所以他们甚至可以安装到Program Files 。 没有UAC提示非pipe理员用户,所以即使他们可以安装应用程序(到用户文件夹)。

这虽然有一些缺点:

  • 有些人在他们的机器上使用不同的pipe理员帐户和非pipe理员帐户,通常使用非pipe理员帐户。 通常,使用非pipe理员帐户启动安装时,当他们获得UAC提示时,他们inputpipe理员帐户的凭据继续。 但是这不适用于我的安装程序,因为没有UAC提示。
  • (过度可疑)具有pipe理员帐号的用户,如果想要安装到用户文件夹,则不能在没有(不需要的)pipe理员权限的情况下启动我的安装程序。

有什么方法可以使创新安装请求权限仅在需要时提升(当用户select安装文件夹只能由pipe理员帐户写入)?

我想在Inno Setup中没有这个设置。 但可能有一个编程解决scheme(Inno Setup Pascal脚本)或某种插件/ DLL。

在Inno安装程序的生命周期中,没有内置的方式提升安装程序。 但是,您可以使用runas动词执行设置过程,并杀死未升级的。 我写的脚本有点棘手,但是展示了一个可能的方法。

警告:

这里使用的代码总是尝试执行高级设置实例; 没有检查海拔是否实际需要(如何决定是否需要另行提问)。 另外,目前我还不能说这样做是否安全。 我不确定Inno安装程序是否(或不会)以某种方式依赖于PrivilegesRequired指令的值。 最后,这个提升的东西只能在相关的Windows版本上执行。 在这个脚本中没有检查这个:

 [Setup] AppName=My Program AppVersion=1.5 DefaultDirName={pf}\My Program PrivilegesRequired=lowest [Code] #ifdef UNICODE #define AW "W" #else #define AW "A" #endif type HINSTANCE = THandle; procedure ExitProcess(uExitCode: UINT); external 'ExitProcess@kernel32.dll stdcall'; function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string; lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE; external 'ShellExecute{#AW}@shell32.dll stdcall'; var Elevated: Boolean; PagesSkipped: Boolean; function CmdLineParamExists(const Value: string): Boolean; var I: Integer; begin Result := False; for I := 1 to ParamCount do if CompareText(ParamStr(I), Value) = 0 then begin Result := True; Exit; end; end; procedure InitializeWizard; begin { initialize our helper variables } Elevated := CmdLineParamExists('/ELEVATE'); PagesSkipped := False; end; function ShouldSkipPage(PageID: Integer): Boolean; begin { if we've executed this instance as elevated, skip pages unless we're } { on the directory selection page } Result := not PagesSkipped and Elevated and (PageID <> wpSelectDir); { if we've reached the directory selection page, set our flag variable } if not Result then PagesSkipped := True; end; function NextButtonClick(CurPageID: Integer): Boolean; var Params: string; RetVal: HINSTANCE; begin Result := True; { if we are on the directory selection page and we are not running the } { instance we've manually elevated, then... } if not Elevated and (CurPageID = wpSelectDir) then begin { pass the already selected directory to the executing parameters and } { include our own custom /ELEVATE parameter which is used to tell the } { setup to skip all the pages and get to the directory selection page } Params := ExpandConstant('/DIR="{app}" /ELEVATE'); { because executing of the setup loader is not possible with ShellExec } { function, we need to use a WinAPI workaround } RetVal := ShellExecute(WizardForm.Handle, 'runas', ExpandConstant('{srcexe}'), Params, '', SW_SHOW); { if elevated executing of this setup succeeded, then... } if RetVal > 32 then begin { exit this non-elevated setup instance } ExitProcess(0); end else { executing of this setup failed for some reason; one common reason may } { be simply closing the UAC dialog } begin { handling of this situation is upon you, this line forces the wizard } { stay on the current page } Result := False; { and possibly show some error message to the user } MsgBox(Format('Elevating of this setup failed. Code: %d', [RetVal]), mbError, MB_OK); end; end; end; 

我的解决scheme基于@ TLama的答案 。

当设置开始不boost时,将会请求boost,但有一些例外:

  • 只有在Windows Vista和更新(虽然它也应该在Windows XP上工作)
  • 升级时,安装程​​序将检查当前用户是否具有对以前安装位置的写入权限。 如果用户具有写权限,则设置不会请求提升。 因此,如果用户以前已将应用程序安装到用户文件夹,则在升级时不会请求提升。

如果用户在新安装时拒绝提升,安装程序将自动回退到“本地应用程序数据”文件夹。 即C:\Users\standard\AppData\Local\AppName

其他改进:

  • 提升的实例不会再要求语言
  • 通过使用PrivilegesRequired=none ,安装程序会将卸载信息写入HKLM ,而不是HKCU
 #define AppId "myapp" #define AppName "MyApp" #define InnoSetupReg \ "Software\Microsoft\Windows\CurrentVersion\Uninstall\" + AppId + "_is1" #define InnoSetupAppPathReg "Inno Setup: App Path" [Setup] AppId={#AppId} PrivilegesRequired=none ... [Code] function IsWinVista: Boolean; begin Result := (GetWindowsVersion >= $06000000); end; function IsElevated: Boolean; begin Result := IsAdminLoggedOn or IsPowerUserLoggedOn; end; function HaveWriteAccessToApp: Boolean; var FileName: string; begin FileName := AddBackslash(WizardDirValue) + 'writetest.tmp'; Result := SaveStringToFile(FileName, 'test', False); if Result then begin Log(Format( 'Have write access to the last installation path [%s]', [WizardDirValue])); DeleteFile(FileName); end else begin Log(Format('Does not have write access to the last installation path [%s]', [ WizardDirValue])); end; end; procedure ExitProcess(uExitCode: UINT); external 'ExitProcess@kernel32.dll stdcall'; function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string; lpParameters: string; lpDirectory: string; nShowCmd: Integer): THandle; external 'ShellExecuteW@shell32.dll stdcall'; function Elevate: Boolean; var I: Integer; RetVal: Integer; Params: string; S: string; begin { Collect current instance parameters } for I := 1 to ParamCount do begin S := ParamStr(I); { Unique log file name for the elevated instance } if CompareText(Copy(S, 1, 5), '/LOG=') = 0 then begin S := S + '-elevated'; end; { Do not pass our /SL5 switch } if CompareText(Copy(S, 1, 5), '/SL5=') <> 0 then begin Params := Params + AddQuotes(S) + ' '; end; end; { ... and add selected language } Params := Params + '/LANG=' + ActiveLanguage; Log(Format('Elevating setup with parameters [%s]', [Params])); RetVal := ShellExecute(0, 'runas', ExpandConstant('{srcexe}'), Params, '', SW_SHOW); Log(Format('Running elevated setup returned [%d]', [RetVal])); Result := (RetVal > 32); { if elevated executing of this setup succeeded, then... } if Result then begin Log('Elevation succeeded'); { exit this non-elevated setup instance } ExitProcess(0); end else begin Log(Format('Elevation failed [%s]', [SysErrorMessage(RetVal)])); end; end; procedure InitializeWizard; var S: string; Upgrade: Boolean; begin Upgrade := RegQueryStringValue(HKLM, '{#InnoSetupReg}', '{#InnoSetupAppPathReg}', S) or RegQueryStringValue(HKCU, '{#InnoSetupReg}', '{#InnoSetupAppPathReg}', S); { elevate } if not IsWinVista then begin Log(Format('This version of Windows [%x] does not support elevation', [ GetWindowsVersion])); end else if IsElevated then begin Log('Running elevated'); end else begin Log('Running non-elevated'); if Upgrade then begin if not HaveWriteAccessToApp then begin Elevate; end; end else begin if not Elevate then begin WizardForm.DirEdit.Text := ExpandConstant('{localappdata}\{#AppName}'); Log(Format('Falling back to local application user folder [%s]', [ WizardForm.DirEdit.Text])); end; end; end; end;