web-dev-qa-db-ja.com

Inno Setupインストーラーが必要な場合にのみ特権の昇格を要求するようにする

Inno Setup インストーラーには PrivilegesRequiredディレクティブ があり、インストーラーの起動時に特権の昇格が必要な場合に制御できます。管理者以外のユーザーでもインストーラーを機能させたい(Program Filesではなく、ユーザーフォルダーにアプリをインストールしても問題ありません)。そのため、PrivilegesRequirednone(文書化されていない値)に設定します。これにより、UACプロンプトポップアップは管理ユーザーのみに表示されるため、Program Filesにもインストールできます。管理者以外のユーザーにはUACプロンプトが表示されないため、ユーザーも(ユーザーフォルダーに)アプリケーションをインストールできます。

ただし、これにはいくつかの欠点があります。

  • 一部の人々は、自分のマシンで個別の管理者アカウントと非管理者アカウントを使用しており、通常は非管理者アカウントで作業しています。一般に、非管理者アカウントを使用してインストールを開始する場合、UACプロンプトが表示されたら、管理者アカウントの資格情報を入力して続行します。しかし、UACプロンプトがないため、これは私のインストーラーでは機能しません。
  • (非常に疑わしい)管理者アカウントを持つユーザーがユーザーフォルダーにインストールしたい場合、(不要な)管理者権限がないとインストーラーを起動できません。

Inno Setupが必要なときにのみ特権を昇格させる方法はありますか(ユーザーが管理者アカウントのみが書き込み可能なインストールフォルダーを選択した場合)

Inno Setupにはこの設定がないと思います。しかし、おそらく、プログラムによる解決策(Inno Setup Pascalスクリプト)または何らかのプラグイン/ DLLがあります。


Inno Setup 6には 非管理インストールモード のサポートが組み込まれていることに注意してください。

24
Martin Prikryl

Inno Setup 6には 非管理インストールモード のサポートが組み込まれています。

基本的に、単純に PrivilegesRequiredOverridesAllowed を設定できます:

[Setup]
PrivilegesRequiredOverridesAllowed=commandline dialog

enter image description here


以下は、 @ TLamaの回答 に基づいた、Inno Setup 5の私の(現在は廃止された)ソリューションです。

昇格せずにセットアップを開始すると、いくつかの例外を除き、昇格が要求されます。

  • 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 '[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 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;
15
Martin Prikryl

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;
9
TLama