Inno Setup - Make Inno Setup Installer report its installation progress status to master installer

前端 未结 1 1857
春和景丽
春和景丽 2020-12-05 22:25

I currently have two Inno Setup installers working around. I need one of them to report its status as a sub installer to another installer even it running with VERYSIL

相关标签:
1条回答
  • 2020-12-05 23:00

    I do not think you need to code fancy IPC stuff for this. Just exchange the information via a temporary file.

    Child installer code:

    [Code]
    
    function SetTimer(
      Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
      external 'SetTimer@user32.dll stdcall';
    
    var
      ProgressFileName: string;
      PrevProgress: Integer;
    
    procedure ReportProgressProc(
      H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
    var
      Progress: Integer;
    begin
      try
        Progress :=
          (WizardForm.ProgressGauge.Position * 100) div WizardForm.ProgressGauge.Max;
        if PrevProgress <> Progress then
        begin
          if not SaveStringToFile(ProgressFileName, IntToStr(Progress), False) then
          begin
            Log(Format('Failed to save progress %d', [Progress]));
          end
            else
          begin
            Log(Format('Saved progress %d', [Progress]));
            PrevProgress := Progress;
          end;
        end;
      except
        Log('Exception saving progress');
      end;
    end;  
    
    procedure InitializeWizard();
    begin
      { When run with /progress=<path> switch, will report progress to that file }
      ProgressFileName := ExpandConstant('{param:progress}');
      if ProgressFileName <> '' then
      begin
        Log(Format('Will write progress to: %s', [ProgressFileName]));
        PrevProgress := -1;
        SetTimer(0, 0, 250, CreateCallback(@ReportProgressProc));
      end;
    end;
    

    Master installer code:

    #define ChildInstaller "mysetup.exe"
    
    [Files]
    Source: {#ChildInstaller}; Flags: dontcopy
    
    [Code]
    
    function SetTimer(
      Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
      external 'SetTimer@user32.dll stdcall';
    function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
      external 'KillTimer@user32.dll stdcall';
    
    var
      ProgressPage: TOutputProgressWizardPage;
      ProgressFileName: string;
    
    procedure UpdateProgressProc(
      H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
    var
      S: AnsiString;
      Progress: Integer;
    begin
      try
        if not LoadStringFromFile(ProgressFileName, S) then
        begin
          Log(Format('Failed to read progress from file %s', [ProgressFileName]));
        end
          else
        begin
          Progress := StrToIntDef(S, -1);
          if (Progress < 0) or (Progress > 100) then
          begin
            Log(Format('Read invalid progress %s', [S]));
          end
            else
          begin
            Log(Format('Read progress %d', [Progress]));
            ProgressPage.SetProgress(Progress, 100);
          end;
        end;
      except
        Log('Exception updating progress');
      end;
    end;
    
    procedure InstallChild;
    var
      ChildInstallerPath: string;
      ChildInstallerParams: string;
      Timer: LongWord;
      InstallError: string;
      ResultCode: Integer;
    begin
      ExtractTemporaryFile('{#ChildInstaller}');
    
      ProgressPage := CreateOutputProgressPage('Running child installer', '');
      ProgressPage.SetProgress(0, 100);
      ProgressPage.Show;
      try
        Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgressProc));
    
        ChildInstallerPath := ExpandConstant('{tmp}\{#ChildInstaller}');
        ProgressFileName := ExpandConstant('{tmp}\progress.txt');
        Log(Format('Expecting progress in %s', [ProgressFileName]));
        ChildInstallerParams := Format('/verysilent /progress="%s"', [ProgressFileName]);
        if not Exec(ChildInstallerPath, ChildInstallerParams, '', SW_SHOW,
                    ewWaitUntilTerminated, ResultCode) then
        begin
          InstallError := 'Cannot start child installer';
        end
          else
        if ResultCode <> 0 then
        begin
          InstallError := Format('Child installer failed with code %d', [ResultCode]);
        end;
      finally
        { Clean up }
        KillTimer(0, Timer);
        ProgressPage.Hide;
        DeleteFile(ProgressFileName);
      end;
    
      if InstallError <> '' then
      begin 
        { RaiseException does not work properly while TOutputProgressWizardPage is shown }
        RaiseException(InstallError);
      end;
    end;
    

    You can use the InstallChild like below, or on any other place of your installer process:

    function NextButtonClick(CurPageID: Integer): Boolean;
    begin
      Result := True;
    
      if CurPageID = wpReady then
      begin
        try
          InstallChild;
        except
          MsgBox(GetExceptionMessage, mbError, MB_OK);
          Result := False;
        end;
      end;
    end;
    

    Another good solution would be to use the PrepareToInstall event function. For an example see my answer to Inno Setup torrent download implementation.


    For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library.


    It might be better to use the TFileStream instead of the LoadStringFromFile and the SaveStringToFile. The TFileStream supports read sharing. With the LoadStringFromFile and the SaveStringToFile, the progress reporting may occasionally temporarily fail, if both sides happen to try to read and write at the same time.

    See Inno Setup LoadStringFromFile fails when file is open in another process.


    This shows how the child and master installer progresses are linked (if the child installer is not running with the /verysilent switch, but with the /silent only):


    If you need to use a standalone progress bar, you can use the following master installer code:

    #define ChildInstaller "mysetup.exe"
    
    [Files]
    Source: {#ChildInstaller}; Flags: dontcopy
    
    [Code]
    
    function SetTimer(
      Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
      external 'SetTimer@user32.dll stdcall';
    function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
      external 'KillTimer@user32.dll stdcall';
    
    var
      ProgressFileName: string;
    
    procedure UpdateProgressProc(
      H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
    var
      S: AnsiString;
      Progress: Integer;
    begin
      try
        if not LoadStringFromFile(ProgressFileName, S) then
        begin
          Log(Format('Failed to read progress from file %s', [ProgressFileName]));
        end
          else
        begin
          Progress := StrToIntDef(S, -1);
          if (Progress < 0) or (Progress > 100) then
          begin
            Log(Format('Read invalid progress %s', [S]));
          end
            else
          begin
            Log(Format('Read progress %d', [Progress]));
            WizardForm.ProgressGauge.Position :=
              Progress * WizardForm.ProgressGauge.Max div 100;
          end;
        end;
      except
        Log('Exception updating progress');
      end;
    end;
    
    procedure InstallChild;
    var
      ChildInstallerPath: string;
      ChildInstallerParams: string;
      Timer: LongWord;
      ResultCode: Integer;
    begin
      ExtractTemporaryFile('{#ChildInstaller}');
    
      try
        Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgressProc));
    
        ChildInstallerPath := ExpandConstant('{tmp}\{#ChildInstaller}');
        ProgressFileName := ExpandConstant('{tmp}\progress.txt');
        Log(Format('Expecting progress in %s', [ProgressFileName]));
        ChildInstallerParams := Format('/verysilent /progress="%s"', [ProgressFileName]);
        if not Exec(ChildInstallerPath, ChildInstallerParams, '', SW_SHOW,
                    ewWaitUntilTerminated, ResultCode) then
        begin
          MsgBox('Cannot start child installer', mbError, MB_OK);
        end
          else
        if ResultCode <> 0 then
        begin
          MsgBox(Format(
            'Child installer failed with code %d', [ResultCode]), mbError, MB_OK);
        end;
      finally
        { Clean up }
        KillTimer(0, Timer);
        DeleteFile(ProgressFileName);
      end;
    end;
    
    procedure CurStepChanged(CurStep: TSetupStep);
    begin
      if CurStep = ssInstall then
      begin
        InstallChild;
      end;
    end;
    
    0 讨论(0)
提交回复
热议问题