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

前端 未结 1 1122
半阙折子戏
半阙折子戏 2020-12-05 22:21

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 NeedsR

相关标签:
1条回答
  • 2020-12-05 23:08

    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

    • dotnetfx45_full_x86_x64.exe (.NET framework 4.5 - off-line installer)
    • NDP462-KB3151800-x86-x64-AllOS-ENU.exe (.NET framework 4.6.2 - off-line installer)

    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.

    0 讨论(0)
提交回复
热议问题