Inno Setup 从 .NET Framework 4.5(或更高版本)安装程序获取进度以更新进度条位置
Inno Setup Get progress from .NET Framework 4.5 (or higher) installer to update progress bar position
我目前正在安装 .NET Framework 4.6.2 作为 PrepareToInstall
事件函数的先决条件,以便我可以获得退出代码、设置 NeedsReboot
状态,或者在安装失败时中止.我的代码在下面,一切正常。
var
PrepareToInstallLabel: TNewStaticText;
PrepareToInstallProgressBar: TNewProgressBar;
intDotNetResultCode: Integer;
CancelWithoutPrompt, AbortInstall: Boolean;
function InitializeSetup(): Boolean;
begin
Result := True;
OverwriteDB := False;
CancelWithoutPrompt := False;
AbortInstall := False;
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
intResultCode: Integer;
strInstallType: String;
begin
if not IsDotNet45Installed and IsWindows7Sp1OrAbove then
begin
HidePrepareToInstallGuiControls;
PrepareToInstallLabel.Caption := 'Installing Microsoft .NET Framework 4.6.2...';
ShowPrepareToInstallGuiControls;
ExtractTemporaryFile('NDP462-KB3151800-x86-x64-AllOS-ENU.exe');
if WizardSilent = True then
begin
strInstallType := '/q';
end
else
begin
strInstallType := '/passive';
end;
Exec(ExpandConstant('{tmp}\NDP462-KB3151800-x86-x64-AllOS-ENU.exe'), strInstallType + ' /norestart', '', SW_SHOW,
ewWaitUntilTerminated, intDotNetResultCode);
if (intDotNetResultCode = 0) or (intDotNetResultCode = 1641) or (intDotNetResultCode = 3010) then
begin
Log('Microsoft .NET Framework 4.6.2 installed successfully.' + #13#10 + 'Exit Code: ' + IntToStr(intDotNetResultCode));
CancelWithoutPrompt := False;
AbortInstall := False;
end
else
begin
if WizardSilent = True then
begin
Log('Microsoft .NET Framework 4.6.2 failed to install.' + #13#10 + 'Exit Code: ' + IntToStr(intDotNetResultCode) + #13#10 + 'Setup aborted.');
end
else
begin
MsgBox('Microsoft .NET Framework 4.6.2 failed to install.' + #13#10 + #13#10 +
'Exit Code: ' + IntToStr(intDotNetResultCode) + #13#10 + #13#10 +
'Setup aborted. Click Next or Cancel to exit, or Back to try again.',
mbCriticalError, MB_OK);
end;
PrepareToInstallProgressBar.Visible := False;
PrepareToInstallLabel.Caption := 'Microsoft .NET Framework 4.6.2 failed to install.' + #13#10 + #13#10 + 'Exit Code: ' + IntToStr(intDotNetResultCode) + #13#10 + #13#10 + 'Setup aborted. Click Next or Cancel to exit, or Back to try again.';
CancelWithoutPrompt := True;
AbortInstall := True;
Abort;
end;
end;
end;
procedure InitializeWizard();
begin
//Define the label for the Preparing to Install page
PrepareToInstallLabel := TNewStaticText.Create(WizardForm);
with PrepareToInstallLabel do
begin
Visible := False;
Parent := WizardForm.PreparingPage;
Left := WizardForm.StatusLabel.Left;
Top := WizardForm.StatusLabel.Top;
end;
//Define Progress Bar for the Preparing to Install Page
PrepareToInstallProgressBar := TNewProgressBar.Create(WizardForm);
with PrepareToInstallProgressBar do
begin
Visible := False;
Parent := WizardForm.PreparingPage;
Left := WizardForm.ProgressGauge.Left;
Top := WizardForm.ProgressGauge.Top;
Width := WizardForm.ProgressGauge.Width;
Height := WizardForm.ProgressGauge.Height;
PrepareToInstallProgressBar.Style := npbstMarquee;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if AbortInstall = True then
begin
Abort;
end;
end;
end;
目前,我使用 /q
或 /passive
将安装类型设置为静默或无人值守,以控制 .NET Framework 安装程序显示的可见 GUI 数量,具体取决于 Inno设置为 运行 并使用 Marquee 风格的进度条来指示正在发生的事情。但是,从 Microsoft 文档 here 看来,可以使用 /pipe
开关让 .NET Framework 安装程序报告其安装进度,这可能允许它以交互方式更新正常的实际进度上的样式进度条。这意味着 .NET Framework 安装程序可以完全隐藏,Inno Setup 用于指示相对进度,这是一个更简洁的解决方案。不幸的是,我不懂 C++,只是一个新手程序员。因此,任何人都可以确认这是否可以通过 Inno Setup 进行,如果可以,如何尝试?
以下显示了来自
的代码的 Pascal Script 实现
How to: Get Progress from the .NET Framework 4.5 Installer
[Files]
Source: "NDP462-KB3151800-x86-x64-AllOS-ENU.exe"; Flags: dontcopy
[Code]
// Change to unique names
const
SectionName = 'MyProgSetup';
EventName = 'MyProgSetupEvent';
const
INFINITE = 65535;
WAIT_OBJECT_0 = 0;
WAIT_TIMEOUT = [=10=]000102;
FILE_MAP_WRITE = [=10=]02;
E_PENDING = 00000A;
S_OK = 0;
MMIO_V45 = 1;
MAX_PATH = 260;
SEE_MASK_NOCLOSEPROCESS = [=10=]000040;
INVALID_HANDLE_VALUE = -1;
PAGE_READWRITE = 4;
MMIO_SIZE = 65536;
type
TMmioDataStructure = record
DownloadFinished: Boolean; // download done yet?
InstallFinished: Boolean; // install done yet?
DownloadAbort: Boolean; // set downloader to abort
InstallAbort: Boolean; // set installer to abort
DownloadFinishedResult: Cardinal; // resultant HRESULT for download
InstallFinishedResult: Cardinal; // resultant HRESULT for install
InternalError: Cardinal;
CurrentItemStep: array[0..MAX_PATH-1] of WideChar;
DownloadSoFar: Byte; // download progress 0 - 255 (0 to 100% done)
InstallSoFar: Byte; // install progress 0 - 255 (0 to 100% done)
// event that chainer 'creates' and chainee 'opens'to sync communications
EventName: array[0..MAX_PATH-1] of WideChar;
Version: Byte; // version of the data structure, set by chainer.
// 0x0 : .Net 4.0
// 0x1 : .Net 4.5
// current message being sent by the chainee, 0 if no message is active
MessageCode: Cardinal;
// chainer's response to current message, 0 if not yet handled
MessageResponse: Cardinal;
// length of the m_messageData field in bytes
MessageDataLength: Cardinal;
// variable length buffer, content depends on m_messageCode
MessageData: array[0..MMIO_SIZE] of Byte;
end;
function CreateFileMapping(
File: THandle; Attributes: Cardinal; Protect: Cardinal;
MaximumSizeHigh: Cardinal; MaximumSizeLow: Cardinal; Name: string): THandle;
external 'CreateFileMappingW@kernel32.dll stdcall';
function CreateEvent(
EventAttributes: Cardinal; ManualReset: Boolean; InitialState: Boolean;
Name: string): THandle;
external 'CreateEventW@kernel32.dll stdcall';
function CreateMutex(
MutexAttributes: Cardinal; InitialOwner: Boolean; Name: string): THandle;
external 'CreateMutexW@kernel32.dll stdcall';
function WaitForSingleObject(
Handle: THandle; Milliseconds: Cardinal): Cardinal;
external 'WaitForSingleObject@kernel32.dll stdcall';
function MapViewOfFile(
FileMappingObject: THandle; DesiredAccess: Cardinal; FileOffsetHigh: Cardinal;
FileOffsetLow: Cardinal; NumberOfBytesToMap: Cardinal): Cardinal;
external 'MapViewOfFile@kernel32.dll stdcall';
function ReleaseMutex(Mutex: THandle): Boolean;
external 'ReleaseMutex@kernel32.dll stdcall';
type
TShellExecuteInfo = record
cbSize: DWORD;
fMask: Cardinal;
Wnd: HWND;
lpVerb: string;
lpFile: string;
lpParameters: string;
lpDirectory: string;
nShow: Integer;
hInstApp: THandle;
lpIDList: DWORD;
lpClass: string;
hkeyClass: THandle;
dwHotKey: DWORD;
hMonitor: THandle;
hProcess: THandle;
end;
function ShellExecuteEx(var lpExecInfo: TShellExecuteInfo): BOOL;
external 'ShellExecuteExW@shell32.dll stdcall';
function GetExitCodeProcess(Process: THandle; var ExitCode: Cardinal): Boolean;
external 'GetExitCodeProcess@kernel32.dll stdcall';
procedure CopyPointerToData(
var Destination: TMmioDataStructure; Source: Cardinal; Length: Cardinal);
external 'RtlMoveMemory@kernel32.dll stdcall';
procedure CopyDataToPointer(
Destination: Cardinal; var Source: TMmioDataStructure; Length: Cardinal);
external 'RtlMoveMemory@kernel32.dll stdcall';
var
FileMapping: THandle;
EventChaineeSend: THandle;
EventChainerSend: THandle;
Mutex: THandle;
Data: TMmioDataStructure;
View: Cardinal;
procedure LockDataMutex;
var
R: Cardinal;
begin
R := WaitForSingleObject(Mutex, INFINITE);
Log(Format('WaitForSingleObject = %d', [Integer(R)]));
if R <> WAIT_OBJECT_0 then
RaiseException('Error waiting for mutex');
end;
procedure UnlockDataMutex;
var
R: Boolean;
begin
R := ReleaseMutex(Mutex);
Log(Format('ReleaseMutex = %d', [Integer(R)]));
if not R then
RaiseException('Error releasing waiting for mutex');
end;
procedure ReadData;
begin
CopyPointerToData(Data, View, MMIO_SIZE);
end;
procedure WriteData;
begin
CopyDataToPointer(View, Data, MMIO_SIZE);
end;
procedure InitializeChainer;
var
I: Integer;
begin
Log('Initializing chainer');
FileMapping :=
CreateFileMapping(
INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, MMIO_SIZE, SectionName);
Log(Format('FileMapping = %d', [Integer(FileMapping)]));
if FileMapping = 0 then
RaiseException('Error creating file mapping');
EventChaineeSend := CreateEvent(0, False, False, EventName);
Log(Format('EventChaineeSend = %d', [Integer(EventChaineeSend)]));
if EventChaineeSend = 0 then
RaiseException('Error creating chainee event');
EventChainerSend := CreateEvent(0, False, False, EventName + '_send');
Log(Format('EventChainerSend = %d', [Integer(EventChainerSend)]));
if EventChainerSend = 0 then
RaiseException('Error creating chainer event');
Mutex := CreateMutex(0, False, EventName + '_mutex');
Log(Format('Mutex = %d', [Integer(Mutex)]));
if Mutex = 0 then
RaiseException('Error creating mutex');
View :=
MapViewOfFile(FileMapping, FILE_MAP_WRITE, 0, 0, 0);
if View = 0 then
RaiseException('Cannot map data view');
Log('Mapped data view');
LockDataMutex;
ReadData;
Log('Initializing data');
for I := 1 to Length(EventName) do
Data.EventName[I - 1] := EventName[I];
Data.EventName[Length(EventName)] := #[=10=];
// Download specific data
Data.DownloadFinished := False;
Data.DownloadSoFar := 0;
Data.DownloadFinishedResult := E_PENDING;
Data.DownloadAbort := False;
// Install specific data
Data.InstallFinished := False;
Data.InstallSoFar := 0;
Data.InstallFinishedResult := E_PENDING;
Data.InstallAbort := False;
Data.InternalError := S_OK;
Data.Version := MMIO_V45;
Data.MessageCode := 0;
Data.MessageResponse := 0;
Data.MessageDataLength := 0;
Log('Initialized data');
WriteData;
UnlockDataMutex;
Log('Initialized chainer');
end;
var
ProgressPage: TOutputProgressWizardPage;
procedure InstallNetFramework;
var
R: Cardinal;
ExecInfo: TShellExecuteInfo;
ExitCode: Cardinal;
InstallError: string;
Completed: Boolean;
Progress: Integer;
begin
ExtractTemporaryFile('NDP462-KB3151800-x86-x64-AllOS-ENU.exe');
// Start the installer using ShellExecuteEx to get process ID
ExecInfo.cbSize := SizeOf(ExecInfo);
ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS;
ExecInfo.Wnd := 0;
ExecInfo.lpFile :=
ExpandConstant('{tmp}\NDP462-KB3151800-x86-x64-AllOS-ENU.exe');
ExecInfo.lpParameters :=
'/pipe ' + SectionName + ' /chainingpackage mysetup /q';
ExecInfo.nShow := SW_HIDE;
if not ShellExecuteEx(ExecInfo) then
RaiseException('Cannot start .NET framework installer');
Log(Format('.NET framework installer started as process %x', [
ExecInfo.hProcess]));
Progress := 0;
ProgressPage.SetProgress(Progress, 100);
ProgressPage.Show;
try
Completed := False;
while not Completed do
begin
// Check if the installer process has finished already
R := WaitForSingleObject(ExecInfo.hProcess, 0);
if R = WAIT_OBJECT_0 then
begin
Log('.NET framework installer completed');
Completed := True;
if not GetExitCodeProcess(ExecInfo.hProcess, ExitCode) then
begin
InstallError := 'Cannot get .NET framework installer exit code';
end
else
begin
Log(Format('Exit code: %d', [Integer(ExitCode)]));
if ExitCode <> 0 then
begin
InstallError :=
Format('.NET framework installer failed with exit code %d', [
ExitCode]);
end;
end;
end
else
if R <> WAIT_TIMEOUT then
begin
InstallError := 'Error waiting for .NET framework installer to complete';
Completed := True;
end
else
begin
// Check if the installer process has signaled progress event
R := WaitForSingleObject(EventChaineeSend, 0);
if R = WAIT_OBJECT_0 then
begin
Log('Got event from the installer');
{ Read progress data }
LockDataMutex;
ReadData;
Log(Format(
'DownloadSoFar = %d, InstallSoFar = %d', [
Data.DownloadSoFar, Data.InstallSoFar]));
Progress := Integer(Data.InstallSoFar) * 100 div 255;
Log(Format('Progress = %d', [Progress]));
UnlockDataMutex;
ProgressPage.SetProgress(Progress, 100);
end
else
if R <> WAIT_TIMEOUT then
begin
InstallError := 'Error waiting for .NET framework installer event';
Completed := True;
end
else
begin
// Seemingly pointless as progress did not change,
// but it pumps a message queue as a side effect
ProgressPage.SetProgress(Progress, 100);
Sleep(100);
end;
end;
end;
finally
ProgressPage.Hide;
end;
if InstallError <> '' then
begin
// RaiseException does not work properly
// while TOutputProgressWizardPage is shown
RaiseException(InstallError);
end;
end;
function InitializeSetup(): Boolean;
begin
InitializeChainer;
Result := True;
end;
procedure InitializeWizard();
begin
ProgressPage := CreateOutputProgressPage('Installing .NET framework', '');
end;
您可以像下面那样使用它,也可以在安装程序的任何其他地方使用它。
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpReady then
begin
try
InstallNetFramework;
except
MsgBox(GetExceptionMessage, mbError, MB_OK);
Result := False;
end;
end;
end;
下面的截图显示了 Inno Setup 中的“进度页面”是如何链接到 .NET framework 安装程序的(当然 .NET framework 安装程序被 /q
开关隐藏了,它只是暂时显示用于获取屏幕截图)。
我已经在
上成功测试了代码
dotnetfx45_full_x86_x64.exe
(.NET Framework 4.5 - 离线安装程序)
NDP462-KB3151800-x86-x64-AllOS-ENU.exe
(.NET Framework 4.6.2 - 离线安装程序)
请注意,代码只考虑了 InstallSoFar
,因为上面的两个安装程序都处于离线状态。对于在线安装程序,也应考虑 DownloadSoFar
。实际上,即使是离线安装程序有时也会下载一些东西。
ShellExecuteEx
代码取自 Inno Setup Exec() function Wait for a limited time。
我目前正在安装 .NET Framework 4.6.2 作为 PrepareToInstall
事件函数的先决条件,以便我可以获得退出代码、设置 NeedsReboot
状态,或者在安装失败时中止.我的代码在下面,一切正常。
var
PrepareToInstallLabel: TNewStaticText;
PrepareToInstallProgressBar: TNewProgressBar;
intDotNetResultCode: Integer;
CancelWithoutPrompt, AbortInstall: Boolean;
function InitializeSetup(): Boolean;
begin
Result := True;
OverwriteDB := False;
CancelWithoutPrompt := False;
AbortInstall := False;
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
intResultCode: Integer;
strInstallType: String;
begin
if not IsDotNet45Installed and IsWindows7Sp1OrAbove then
begin
HidePrepareToInstallGuiControls;
PrepareToInstallLabel.Caption := 'Installing Microsoft .NET Framework 4.6.2...';
ShowPrepareToInstallGuiControls;
ExtractTemporaryFile('NDP462-KB3151800-x86-x64-AllOS-ENU.exe');
if WizardSilent = True then
begin
strInstallType := '/q';
end
else
begin
strInstallType := '/passive';
end;
Exec(ExpandConstant('{tmp}\NDP462-KB3151800-x86-x64-AllOS-ENU.exe'), strInstallType + ' /norestart', '', SW_SHOW,
ewWaitUntilTerminated, intDotNetResultCode);
if (intDotNetResultCode = 0) or (intDotNetResultCode = 1641) or (intDotNetResultCode = 3010) then
begin
Log('Microsoft .NET Framework 4.6.2 installed successfully.' + #13#10 + 'Exit Code: ' + IntToStr(intDotNetResultCode));
CancelWithoutPrompt := False;
AbortInstall := False;
end
else
begin
if WizardSilent = True then
begin
Log('Microsoft .NET Framework 4.6.2 failed to install.' + #13#10 + 'Exit Code: ' + IntToStr(intDotNetResultCode) + #13#10 + 'Setup aborted.');
end
else
begin
MsgBox('Microsoft .NET Framework 4.6.2 failed to install.' + #13#10 + #13#10 +
'Exit Code: ' + IntToStr(intDotNetResultCode) + #13#10 + #13#10 +
'Setup aborted. Click Next or Cancel to exit, or Back to try again.',
mbCriticalError, MB_OK);
end;
PrepareToInstallProgressBar.Visible := False;
PrepareToInstallLabel.Caption := 'Microsoft .NET Framework 4.6.2 failed to install.' + #13#10 + #13#10 + 'Exit Code: ' + IntToStr(intDotNetResultCode) + #13#10 + #13#10 + 'Setup aborted. Click Next or Cancel to exit, or Back to try again.';
CancelWithoutPrompt := True;
AbortInstall := True;
Abort;
end;
end;
end;
procedure InitializeWizard();
begin
//Define the label for the Preparing to Install page
PrepareToInstallLabel := TNewStaticText.Create(WizardForm);
with PrepareToInstallLabel do
begin
Visible := False;
Parent := WizardForm.PreparingPage;
Left := WizardForm.StatusLabel.Left;
Top := WizardForm.StatusLabel.Top;
end;
//Define Progress Bar for the Preparing to Install Page
PrepareToInstallProgressBar := TNewProgressBar.Create(WizardForm);
with PrepareToInstallProgressBar do
begin
Visible := False;
Parent := WizardForm.PreparingPage;
Left := WizardForm.ProgressGauge.Left;
Top := WizardForm.ProgressGauge.Top;
Width := WizardForm.ProgressGauge.Width;
Height := WizardForm.ProgressGauge.Height;
PrepareToInstallProgressBar.Style := npbstMarquee;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if AbortInstall = True then
begin
Abort;
end;
end;
end;
目前,我使用 /q
或 /passive
将安装类型设置为静默或无人值守,以控制 .NET Framework 安装程序显示的可见 GUI 数量,具体取决于 Inno设置为 运行 并使用 Marquee 风格的进度条来指示正在发生的事情。但是,从 Microsoft 文档 here 看来,可以使用 /pipe
开关让 .NET Framework 安装程序报告其安装进度,这可能允许它以交互方式更新正常的实际进度上的样式进度条。这意味着 .NET Framework 安装程序可以完全隐藏,Inno Setup 用于指示相对进度,这是一个更简洁的解决方案。不幸的是,我不懂 C++,只是一个新手程序员。因此,任何人都可以确认这是否可以通过 Inno Setup 进行,如果可以,如何尝试?
以下显示了来自
的代码的 Pascal Script 实现
How to: Get Progress from the .NET Framework 4.5 Installer
[Files]
Source: "NDP462-KB3151800-x86-x64-AllOS-ENU.exe"; Flags: dontcopy
[Code]
// Change to unique names
const
SectionName = 'MyProgSetup';
EventName = 'MyProgSetupEvent';
const
INFINITE = 65535;
WAIT_OBJECT_0 = 0;
WAIT_TIMEOUT = [=10=]000102;
FILE_MAP_WRITE = [=10=]02;
E_PENDING = 00000A;
S_OK = 0;
MMIO_V45 = 1;
MAX_PATH = 260;
SEE_MASK_NOCLOSEPROCESS = [=10=]000040;
INVALID_HANDLE_VALUE = -1;
PAGE_READWRITE = 4;
MMIO_SIZE = 65536;
type
TMmioDataStructure = record
DownloadFinished: Boolean; // download done yet?
InstallFinished: Boolean; // install done yet?
DownloadAbort: Boolean; // set downloader to abort
InstallAbort: Boolean; // set installer to abort
DownloadFinishedResult: Cardinal; // resultant HRESULT for download
InstallFinishedResult: Cardinal; // resultant HRESULT for install
InternalError: Cardinal;
CurrentItemStep: array[0..MAX_PATH-1] of WideChar;
DownloadSoFar: Byte; // download progress 0 - 255 (0 to 100% done)
InstallSoFar: Byte; // install progress 0 - 255 (0 to 100% done)
// event that chainer 'creates' and chainee 'opens'to sync communications
EventName: array[0..MAX_PATH-1] of WideChar;
Version: Byte; // version of the data structure, set by chainer.
// 0x0 : .Net 4.0
// 0x1 : .Net 4.5
// current message being sent by the chainee, 0 if no message is active
MessageCode: Cardinal;
// chainer's response to current message, 0 if not yet handled
MessageResponse: Cardinal;
// length of the m_messageData field in bytes
MessageDataLength: Cardinal;
// variable length buffer, content depends on m_messageCode
MessageData: array[0..MMIO_SIZE] of Byte;
end;
function CreateFileMapping(
File: THandle; Attributes: Cardinal; Protect: Cardinal;
MaximumSizeHigh: Cardinal; MaximumSizeLow: Cardinal; Name: string): THandle;
external 'CreateFileMappingW@kernel32.dll stdcall';
function CreateEvent(
EventAttributes: Cardinal; ManualReset: Boolean; InitialState: Boolean;
Name: string): THandle;
external 'CreateEventW@kernel32.dll stdcall';
function CreateMutex(
MutexAttributes: Cardinal; InitialOwner: Boolean; Name: string): THandle;
external 'CreateMutexW@kernel32.dll stdcall';
function WaitForSingleObject(
Handle: THandle; Milliseconds: Cardinal): Cardinal;
external 'WaitForSingleObject@kernel32.dll stdcall';
function MapViewOfFile(
FileMappingObject: THandle; DesiredAccess: Cardinal; FileOffsetHigh: Cardinal;
FileOffsetLow: Cardinal; NumberOfBytesToMap: Cardinal): Cardinal;
external 'MapViewOfFile@kernel32.dll stdcall';
function ReleaseMutex(Mutex: THandle): Boolean;
external 'ReleaseMutex@kernel32.dll stdcall';
type
TShellExecuteInfo = record
cbSize: DWORD;
fMask: Cardinal;
Wnd: HWND;
lpVerb: string;
lpFile: string;
lpParameters: string;
lpDirectory: string;
nShow: Integer;
hInstApp: THandle;
lpIDList: DWORD;
lpClass: string;
hkeyClass: THandle;
dwHotKey: DWORD;
hMonitor: THandle;
hProcess: THandle;
end;
function ShellExecuteEx(var lpExecInfo: TShellExecuteInfo): BOOL;
external 'ShellExecuteExW@shell32.dll stdcall';
function GetExitCodeProcess(Process: THandle; var ExitCode: Cardinal): Boolean;
external 'GetExitCodeProcess@kernel32.dll stdcall';
procedure CopyPointerToData(
var Destination: TMmioDataStructure; Source: Cardinal; Length: Cardinal);
external 'RtlMoveMemory@kernel32.dll stdcall';
procedure CopyDataToPointer(
Destination: Cardinal; var Source: TMmioDataStructure; Length: Cardinal);
external 'RtlMoveMemory@kernel32.dll stdcall';
var
FileMapping: THandle;
EventChaineeSend: THandle;
EventChainerSend: THandle;
Mutex: THandle;
Data: TMmioDataStructure;
View: Cardinal;
procedure LockDataMutex;
var
R: Cardinal;
begin
R := WaitForSingleObject(Mutex, INFINITE);
Log(Format('WaitForSingleObject = %d', [Integer(R)]));
if R <> WAIT_OBJECT_0 then
RaiseException('Error waiting for mutex');
end;
procedure UnlockDataMutex;
var
R: Boolean;
begin
R := ReleaseMutex(Mutex);
Log(Format('ReleaseMutex = %d', [Integer(R)]));
if not R then
RaiseException('Error releasing waiting for mutex');
end;
procedure ReadData;
begin
CopyPointerToData(Data, View, MMIO_SIZE);
end;
procedure WriteData;
begin
CopyDataToPointer(View, Data, MMIO_SIZE);
end;
procedure InitializeChainer;
var
I: Integer;
begin
Log('Initializing chainer');
FileMapping :=
CreateFileMapping(
INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, MMIO_SIZE, SectionName);
Log(Format('FileMapping = %d', [Integer(FileMapping)]));
if FileMapping = 0 then
RaiseException('Error creating file mapping');
EventChaineeSend := CreateEvent(0, False, False, EventName);
Log(Format('EventChaineeSend = %d', [Integer(EventChaineeSend)]));
if EventChaineeSend = 0 then
RaiseException('Error creating chainee event');
EventChainerSend := CreateEvent(0, False, False, EventName + '_send');
Log(Format('EventChainerSend = %d', [Integer(EventChainerSend)]));
if EventChainerSend = 0 then
RaiseException('Error creating chainer event');
Mutex := CreateMutex(0, False, EventName + '_mutex');
Log(Format('Mutex = %d', [Integer(Mutex)]));
if Mutex = 0 then
RaiseException('Error creating mutex');
View :=
MapViewOfFile(FileMapping, FILE_MAP_WRITE, 0, 0, 0);
if View = 0 then
RaiseException('Cannot map data view');
Log('Mapped data view');
LockDataMutex;
ReadData;
Log('Initializing data');
for I := 1 to Length(EventName) do
Data.EventName[I - 1] := EventName[I];
Data.EventName[Length(EventName)] := #[=10=];
// Download specific data
Data.DownloadFinished := False;
Data.DownloadSoFar := 0;
Data.DownloadFinishedResult := E_PENDING;
Data.DownloadAbort := False;
// Install specific data
Data.InstallFinished := False;
Data.InstallSoFar := 0;
Data.InstallFinishedResult := E_PENDING;
Data.InstallAbort := False;
Data.InternalError := S_OK;
Data.Version := MMIO_V45;
Data.MessageCode := 0;
Data.MessageResponse := 0;
Data.MessageDataLength := 0;
Log('Initialized data');
WriteData;
UnlockDataMutex;
Log('Initialized chainer');
end;
var
ProgressPage: TOutputProgressWizardPage;
procedure InstallNetFramework;
var
R: Cardinal;
ExecInfo: TShellExecuteInfo;
ExitCode: Cardinal;
InstallError: string;
Completed: Boolean;
Progress: Integer;
begin
ExtractTemporaryFile('NDP462-KB3151800-x86-x64-AllOS-ENU.exe');
// Start the installer using ShellExecuteEx to get process ID
ExecInfo.cbSize := SizeOf(ExecInfo);
ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS;
ExecInfo.Wnd := 0;
ExecInfo.lpFile :=
ExpandConstant('{tmp}\NDP462-KB3151800-x86-x64-AllOS-ENU.exe');
ExecInfo.lpParameters :=
'/pipe ' + SectionName + ' /chainingpackage mysetup /q';
ExecInfo.nShow := SW_HIDE;
if not ShellExecuteEx(ExecInfo) then
RaiseException('Cannot start .NET framework installer');
Log(Format('.NET framework installer started as process %x', [
ExecInfo.hProcess]));
Progress := 0;
ProgressPage.SetProgress(Progress, 100);
ProgressPage.Show;
try
Completed := False;
while not Completed do
begin
// Check if the installer process has finished already
R := WaitForSingleObject(ExecInfo.hProcess, 0);
if R = WAIT_OBJECT_0 then
begin
Log('.NET framework installer completed');
Completed := True;
if not GetExitCodeProcess(ExecInfo.hProcess, ExitCode) then
begin
InstallError := 'Cannot get .NET framework installer exit code';
end
else
begin
Log(Format('Exit code: %d', [Integer(ExitCode)]));
if ExitCode <> 0 then
begin
InstallError :=
Format('.NET framework installer failed with exit code %d', [
ExitCode]);
end;
end;
end
else
if R <> WAIT_TIMEOUT then
begin
InstallError := 'Error waiting for .NET framework installer to complete';
Completed := True;
end
else
begin
// Check if the installer process has signaled progress event
R := WaitForSingleObject(EventChaineeSend, 0);
if R = WAIT_OBJECT_0 then
begin
Log('Got event from the installer');
{ Read progress data }
LockDataMutex;
ReadData;
Log(Format(
'DownloadSoFar = %d, InstallSoFar = %d', [
Data.DownloadSoFar, Data.InstallSoFar]));
Progress := Integer(Data.InstallSoFar) * 100 div 255;
Log(Format('Progress = %d', [Progress]));
UnlockDataMutex;
ProgressPage.SetProgress(Progress, 100);
end
else
if R <> WAIT_TIMEOUT then
begin
InstallError := 'Error waiting for .NET framework installer event';
Completed := True;
end
else
begin
// Seemingly pointless as progress did not change,
// but it pumps a message queue as a side effect
ProgressPage.SetProgress(Progress, 100);
Sleep(100);
end;
end;
end;
finally
ProgressPage.Hide;
end;
if InstallError <> '' then
begin
// RaiseException does not work properly
// while TOutputProgressWizardPage is shown
RaiseException(InstallError);
end;
end;
function InitializeSetup(): Boolean;
begin
InitializeChainer;
Result := True;
end;
procedure InitializeWizard();
begin
ProgressPage := CreateOutputProgressPage('Installing .NET framework', '');
end;
您可以像下面那样使用它,也可以在安装程序的任何其他地方使用它。
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpReady then
begin
try
InstallNetFramework;
except
MsgBox(GetExceptionMessage, mbError, MB_OK);
Result := False;
end;
end;
end;
下面的截图显示了 Inno Setup 中的“进度页面”是如何链接到 .NET framework 安装程序的(当然 .NET framework 安装程序被 /q
开关隐藏了,它只是暂时显示用于获取屏幕截图)。
我已经在
上成功测试了代码dotnetfx45_full_x86_x64.exe
(.NET Framework 4.5 - 离线安装程序)NDP462-KB3151800-x86-x64-AllOS-ENU.exe
(.NET Framework 4.6.2 - 离线安装程序)
请注意,代码只考虑了 InstallSoFar
,因为上面的两个安装程序都处于离线状态。对于在线安装程序,也应考虑 DownloadSoFar
。实际上,即使是离线安装程序有时也会下载一些东西。
ShellExecuteEx
代码取自 Inno Setup Exec() function Wait for a limited time。