Делать установку прав доступа Inno Setup только при необходимости
Установщик Inno Setup имеет PrivilegesRequired
директива, которая может использоваться для контроля, если требуется повышение привилегий, при запуске установщика. Я хочу, чтобы мой установщик работал даже для пользователей без прав администратора (нет проблем с установкой приложения в папку пользователя, вместо Program Files
). Итак, я установил PrivilegesRequired
в none
(недокументированное значение). Это делает всплывающее окно UAC всплывающим только для пользователей с правами администратора, поэтому они могут установить их даже на Program Files
, Нет запроса UAC для пользователей без прав администратора, поэтому даже они могут установить приложение (в папку пользователя).
Это имеет некоторые недостатки:
- Некоторые люди используют разные учетные записи администратора и не-администратора на своих машинах, обычно работая с учетной записью без прав администратора. Как правило, при запуске установки с использованием учетной записи без прав администратора, когда они получают приглашение UAC, они вводят учетные данные для продолжения учетной записи администратора. Но это не будет работать с моим установщиком, потому что нет запроса UAC.
- (Чрезмерно подозрительно) люди с учетной записью администратора, которые хотят установить в папку пользователя, не могут запустить мой установщик без (ненужных) прав администратора.
Есть ли какой-нибудь способ повысить привилегии запроса Inno Setup только тогда, когда это необходимо (когда пользователь выбирает папку установки, доступную для записи только учетной записи администратора)?
Я предполагаю, что нет никаких настроек для этого в Inno Setup. Но, возможно, есть программное решение (сценарии Inno Setup Pascal) или какой-то плагин /DLL.
2 ответа
Мое решение основано на ответе @TLama.
Когда установка запускается без повышения прав, она запрашивает повышение прав, за некоторыми исключениями:
- Только в 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 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 IsAdminLoggedOn 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;
В Inno Setup нет встроенного способа условного повышения уровня процесса установки в течение его срока службы. Однако вы можете выполнить процесс установки, используя runas
глагол и убить невознесенного. Сценарий, который я написал, немного сложен, но показывает, как это сделать.
Предупреждение:
Код, используемый здесь, пытается выполнить экземпляр установки с повышенными правами всегда; нет никакой проверки, требуется ли повышение на самом деле или нет (как решить, требуется ли повышение, необязательно, задайте в отдельном вопросе, пожалуйста). Кроме того, я не могу сказать в настоящее время, безопасно ли делать такое ручное повышение. Я не уверен, что Inno Setup не (или не будет) полагаться на значение 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;