web-dev-qa-db-fra.com

Faire l'élévation des privilèges du programme d'installation d'Inno Setup uniquement en cas de besoin

Inno Setup l'installateur a la directive PrivilegesRequired qui peut être utilisée pour contrôler, si l'élévation des privilèges est requise, lorsque l'installateur démarre. Je souhaite que mon programme d'installation fonctionne même pour les utilisateurs non administrateurs (aucun problème concernant l'installation de mon application dans le dossier utilisateur, au lieu de Program Files). J'ai donc défini le PrivilegesRequired sur none (valeur non documentée). Cela rend le popup UAC Prompt réservé aux utilisateurs administrateurs, afin qu'ils puissent même s'installer sur le Program Files. Pas d'invite UAC pour les utilisateurs non administrateurs, donc même eux peuvent installer l'application (dans le dossier utilisateur).

Cela présente cependant certains inconvénients:

  • Certaines personnes utilisent des comptes administrateur et non administrateur distincts sur leurs machines, travaillant normalement avec un compte non administrateur. En général, lors du lancement de l'installation à l'aide d'un compte non administrateur, lorsqu'ils reçoivent l'invite UAC, ils saisissent les informations d'identification pour que le compte administrateur puisse continuer. Mais cela ne fonctionnera pas avec mon programme d'installation, car il n'y a pas d'invite UAC.
  • Les personnes (trop suspectes) avec un compte administrateur, qui souhaitent installer dans le dossier utilisateur, ne peuvent pas lancer mon programme d'installation sans les privilèges d'administrateur (non nécessaires).

Existe-t-il un moyen de rendre l'élévation des privilèges de demande d'Inno Setup uniquement en cas de besoin (lorsque l'utilisateur sélectionne le dossier d'installation accessible en écriture par le compte administrateur uniquement)?

Je suppose qu'il n'y a aucun paramètre pour cela dans Inno Setup. Mais il existe peut-être une solution de programmation (script Inno Setup Pascal) ou une sorte de plugin/DLL.


Notez que Inno Setup 6 a un support intégré pour mode d'installation non administratif .

24
Martin Prikryl

Inno Setup 6 a un support intégré pour mode d'installation non administratif .

Fondamentalement, vous pouvez simplement définir PrivilegesRequiredOverridesAllowed :

[Setup]
PrivilegesRequiredOverridesAllowed=commandline dialog

enter image description here


Ce qui suit est ma solution (désormais obsolète) pour Inno Setup 5, basée sur @ TLama's answer .

Lorsque la configuration est lancée sans élévation, elle demandera une élévation, à quelques exceptions près:

  • Uniquement sur Windows Vista et plus récent (bien que cela devrait fonctionner sur Windows XP aussi)
  • Lors de la mise à niveau, le programme d'installation vérifie si l'utilisateur actuel a un accès en écriture à l'emplacement d'installation précédent. Si l'utilisateur a l'accès en écriture, la configuration ne demandera pas l'élévation. Donc, si l'utilisateur a déjà installé l'application dans le dossier utilisateur, l'élévation ne sera pas demandée lors de la mise à niveau.

Si l'utilisateur rejette l'élévation lors d'une nouvelle installation, le programme d'installation reviendra automatiquement au dossier "données d'application locale". C'est à dire. C:\Users\standard\AppData\Local\AppName.

Autres améliorations:

  • l'instance élevée ne demandera plus la langue
  • en utilisant PrivilegesRequired=none, le programme d'installation écrit les informations de désinstallation dans HKLM, lorsqu'il est élevé, et non dans 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

Il n'y a aucun moyen intégré d'élévation conditionnelle du processus de configuration pendant sa durée de vie dans Inno Setup. Cependant, vous pouvez exécuter le processus d'installation en utilisant le verbe runas et tuer le verbe non élevé. Le script que j'ai écrit est un peu délicat, mais montre une façon possible de le faire.

Avertissement:

Le code utilisé ici tente d'exécuter toujours l'instance d'installation élevée; il n'y a pas de vérification si l'élévation est réellement requise ou non (comment décider si l'élévation est nécessaire, éventuellement demander dans une question séparée, s'il vous plaît). De plus, je ne peux pas dire pour le moment s'il est sécuritaire de faire une telle élévation manuelle. Je ne sais pas si Inno Setup ne s'appuie pas (ou ne compte pas) sur la valeur de la directive PrivilegesRequired d'une manière ou d'une autre. Et enfin, ce truc d'élévation ne devrait être exécuté que sur les versions Windows connexes. Aucune vérification n'est effectuée dans ce script:

[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