2014-02-04 5 views
17

Inno Setup Установщик имеет PrivilegesRequired directive, который может использоваться для управления, если требуется повышение привилегий, когда установщик запускается. Я хочу, чтобы мой установщик работал даже для пользователей, не являющихся администраторами (никаких проблем с установкой моего приложения в папку пользователя, а не с Program Files). Поэтому я установил PrivilegesRequired в none (недокументированное значение). Это приводит к появлению всплывающего окна UAC для пользователей admin, поэтому их можно установить даже на Program Files. Нет приглашений UAC для пользователей, не являющихся администраторами, поэтому даже они могут установить приложение (в папку пользователя).Сделать настройку привилегий установщика Inno Setup только при необходимости

Это имеет некоторые недостатки, хотя:

  • Некоторые люди используют отдельные учетные записи администратора и не администратора на своих машинах, работающих с не учетной записью администратора нормально. В общем, при запуске установки с использованием учетной записи не-администратора, когда они получают приглашение UAC, они вводят учетные данные для учетной записи администратора для продолжения. Но это не будет работать с моим установщиком, потому что нет приглашения UAC.
  • (Слишком подозрительные) люди с учетной записью администратора, которые хотят установить в папку пользователя, не могут запускать мой установщик без (не необходимых) прав администратора.

Есть ли способ сделать правку привилегий запроса Inno Setup только тогда, когда это необходимо (когда пользователь выбирает папку установки, доступную только для учетной записи администратора)?

Я предполагаю, что в Inno Setup нет настроек для этого. Но, возможно, есть программное решение (скрипты Inno Setup Pascal) или какой-то плагин/DLL.

+0

Вы можете выполнить установку с помощью 'PrivilegesRequired == none' из Windows Shell, используя' runas' глагол. Но когда и как вы решаете, стоит ли его повышать или нет? – TLama

+0

Я не собираюсь выполнять установщик. Пользователь будет. Поэтому он должен быть прозрачным. Решение должно быть выполнено установщиком на основе (в том числе) установочной папки (при установке в «Program Files», требуется высота, когда установка на рабочий стол [глупый пример] не требуется). В основном, что делает установщик Windows/MSI. –

ответ

4

Мое решение основано на @TLama's answer.

Когда установка запускается, не приподнят, он будет запрашивать повышение, с некоторыми исключениями:

  • только на 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 '[email protected] stdcall'; 
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string; 
    lpParameters: string; lpDirectory: string; nShowCmd: Integer): THandle; 
    external '[email protected] 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; 
+0

Фактически InnoSetup уже предоставляет функцию поддержки с именем ShellExec, которая маршалирует [email protected] Итак, этот 'Результат: = ShellExec ('runas', ExpandConstant ('{srcexe}'), Params, '', SW_SHOW, ewWaitUntilIdle, RetVal);' должен работать одинаково хорошо. – Opher

+1

Вы не можете использовать это. Реализация «ShellExec» явно не позволяет выполнять установку установщика. См. Также комментарий в ответе @TLama. –

+0

@Martin: Есть ли причина, по которой это, или форма этого, не может быть включена в сборку Inno? –

7

В 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 '[email protected] 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; 
+0

Чтобы решить, нужно ли вам поднять процесс или нет, это немного сложнее для перевода в Паскальный скрипт Inno Setup. Самый простой (и, на мой взгляд, самый безопасный) способ - это создать папку или, если она уже существует, написать там файл. Могут возникнуть ситуации, когда вложенная папка Program Files может предоставить доступ на запись, или вложенная папка не Program Files нет. Но это действительно тема, которая заслуживает своего места в другом вопросе. – TLama

+0

Спасибо за ваш ответ. Я думал, что можно повысить привилегии в процессе. Но теперь я вижу, что это не так: http://stackoverflow.com/questions/573086/how-to-elevate-privileges-only-when-required –

+0

Добро пожаловать! Ну, даже в установках установщика Windows вы можете увидеть, как «окно мастера было заново создано», которое фактически запускает новый повышенный процесс и перемещает мастер на страницу, где вы были с убитым невыполненным. – TLama

Смежные вопросы