Inno Setup Get progress from .NET Framework 4.5 (or higher) installer to update progress bar position

匿名 (未验证) 提交于 2019-12-03 07:50:05

问题:

I am currently installing .NET Framework 4.6.2 as a prerequisite in the PrepareToInstall event function so that I can obtain the exit code, set the NeedsReboot status, or abort if installation fails. My code is below and this is all working fine.

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; 

At the moment, I am setting the installation type to either silent or unattended using /q or /passive to control the amount of visible GUI the .NET Framework installer displays, depending on how Inno Setup is running and using a Marquee style progress bar to indicate that something is happening. However, from the Microsoft documentation here, it appears that it is possible to get the .NET Framework installer to report it's install progress back, using the /pipe switch, which might allow it to interactively update a normal style progress bar on the actual progress. This would mean that the .NET Framework installer could be hidden completely and Inno Setup used to indicate the relative progress, which is a much tidier solution. Unfortunately, I do not know C++ and am only a novice programmer. Therefore, can anyone confirm if this is possible to do with Inno Setup and, if so, how it might be attempted?

回答1:

The following shows Pascal Script implementation of the code from
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, 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)] := #$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; 

You can use it 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       InstallNetFramework;     except       MsgBox(GetExceptionMessage, mbError, MB_OK);       Result := False;     end;   end; end; 

The following screenshot shows how the "progress page" in Inno Setup is linked to the .NET framework installer (of course the .NET framework installer is hidden by the /q switch, it was just temporarily shown for purposes of obtaining the screenshot).


I've successfully tested the code on

Note that the code takes into account the InstallSoFar only as both installers above are off-line. For on-line installers, DownloadSoFar should be taken into account too. And actually even off-line installers do sometime download something.


The ShellExecuteEx code taken from Inno Setup Exec() function Wait for a limited time.



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!