使 Inno Setup Installer 向主安装程序报告其安装进度状态 [英] Make Inno Setup Installer report its installation progress status to master 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屋!