使 Inno Setup Installer 向主安装程序报告其安装进度状态 [英] Make Inno Setup Installer report its installation progress status to master installer

查看:27
本文介绍了使 Inno Setup Installer 向主安装程序报告其安装进度状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前有两个 Inno Setup 安装程序.我需要其中一个将其作为子安装程序的状态报告给另一个安装程序,即使它使用 VERYSILENT 命令运行.

我需要这个来根据子安装程序的安装进度在我的主安装程序中显示一个进度条,因为我不想要任何无限(字幕)进度条.

我还了解了


如果您需要使用独立的进度条,您可以使用以下主安装程序代码:

#define ChildInstaller "mysetup.exe";[文件]来源:{#ChildInstaller};标志:不要复制[代码]函数设置定时器(Wnd:长字;IDEvent, Elapse: LongWord;TimerFunc: LongWord): LongWord;外部'SetTimer@user32.dll stdcall';函数 KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;外部'KillTimer@user32.dll stdcall';无功进度文件名:字符串;过程 UpdateProgressProc(H:长字;消息:长字;事件:长字;时间:长字);无功S: AnsiString;进度:整数;开始尝试如果不是 LoadStringFromFile(ProgressFileName, S) 那么开始Log(Format('从文件 %s 读取进度失败', [ProgressFileName]));结尾别的开始进度:= StrToIntDef(S, -1);如果 (Progress <0) 或 (Progress > 100) 那么开始Log(Format('读取无效进度 %s', [S]));结尾别的开始Log(Format('读取进度 %d', [Progress]));WizardForm.ProgressGauge.Position :=进度 * WizardForm.ProgressGauge.Max div 100;结尾;结尾;除了Log('异常更新进度');结尾;结尾;程序安装儿童;无功ChildInstallerPath:字符串;ChildInstallerParams:字符串;定时器:长字;结果代码:整数;开始ExtractTemporaryFile('{#ChildInstaller}');尝试定时器 := 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]);如果不是 Exec(ChildInstallerPath, ChildInstallerParams, '', SW_SHOW,ewWaitUntilTerminated, ResultCode) 然后开始MsgBox('无法启动子安装程序', mbError, MB_OK);结尾别的如果结果代码 <>0 那么开始MsgBox(格式('子安装程序失败,代码为 %d', [ResultCode]), mbError, MB_OK);结尾;最后{ 清理 }KillTimer(0, 定时器);删除文件(进度文件名);结尾;结尾;过程 CurStepChanged(CurStep: TSetupStep);开始如果 CurStep = ssInstall 那么开始安装儿童;结尾;结尾;

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 VERYSILENT command.

I need this to display a progress bar in my main installer according to sub installer's installation progress because I don't want any infinite (marquee) progress bars for this.

I also read about IPC Mechanism in Delphi. How can I add this communication abilities like pumps to Inno Setup source code? Any tips for starting?

Thanks in advance.

解决方案

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;

这篇关于使 Inno Setup Installer 向主安装程序报告其安装进度状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆