inno-setupuacprivilegespascalscriptelevated-privileges

Make Inno Setup installer request privileges elevation only when needed


Inno Setup installer has the PrivilegesRequired directive that can be used to control, if privileges elevation is required, when installer is starting. I want my installer to work even for non-admin users (no problem about installing my app to user folder, instead of the Program Files). So I set the PrivilegesRequired to none (undocumented value). This makes UAC prompt popup for admin users only, so they can install even to the Program Files. No UAC prompt for non-admin users, so even them can install the application (to user folder).

This has some drawbacks though:

Is there some way to make Inno Setup request privileges elevation only when needed (when user selects installation folder writable by admin account only)?

I assume there's no setting for this in Inno Setup. But possibly, there's a programmatic solution (Inno Setup Pascal scripting) or some kind of plugin/DLL.


Note that Inno Setup 6 has a built-in support for non-administrative install mode.


Solution

  • Inno Setup 6 has a built-in support for non-administrative install mode.

    Basically, you can simply set PrivilegesRequiredOverridesAllowed:

    [Setup]
    PrivilegesRequiredOverridesAllowed=dialog
    

    enter image description here

    Additionally, you will likely want to use the auto* variants of the constants. Notably the {autopf} for the DefaultDirName.

    [Setup]
    DefaultDirName={autopf}\My Program
    

    The following is my (now obsolete) solution for Inno Setup 5, based on @TLama's answer.

    When the setup is started non-elevated, it will request elevation, with some exceptions:

    If the user rejects the elevation on a new install, the installer will automatically fall back to "local application data" folder. I.e. C:\Users\standard\AppData\Local\AppName.

    Other improvements:

    #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;