我目前正在安装.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
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 = $00000102; FILE_MAP_WRITE = $0002; E_PENDING = $8000000A; S_OK = 0; MMIO_V45 = 1; MAX_PATH = 260; SEE_MASK_NOCLOSEPROCESS = $00000040; 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,PAGE_READWRITE,MMIO_SIZE,SectionName); Log(Format('FileMapping = %d',[Integer(FileMapping)])); if FileMapping = 0 then RaiseException('Error creating file mapping'); EventChaineeSend := CreateEvent(0,False,EventName); Log(Format('EventChaineeSend = %d',[Integer(EventChaineeSend)])); if EventChaineeSend = 0 then RaiseException('Error creating chainee event'); EventChainerSend := CreateEvent(0,EventName + '_send'); Log(Format('EventChainerSend = %d',[Integer(EventChainerSend)])); if EventChainerSend = 0 then RaiseException('Error creating chainer event'); Mutex := CreateMutex(0,EventName + '_mutex'); Log(Format('Mutex = %d',[Integer(Mutex)])); if Mutex = 0 then RaiseException('Error creating mutex'); View := MapViewOfFile(FileMapping,FILE_MAP_WRITE,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)] := #$00; { 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; { Displaying indefinite progress while the framework installer is initializing } ProgressPage.ProgressBar.Style := npbstMarquee; 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; { Once we get any progress data,switch to definite progress display } ProgressPage.ProgressBar.Style := npbstNormal; 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框架安装程序(当然,.NET框架安装程序被/ 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.