Inno Setup从.NET Framework 4.5(或更高版本)安装程序获取进度以更新进度条位置

时间:2016-08-31 11:06:59

标签: inno-setup pascalscript

我目前正在安装.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 Setup的设置方式运行并使用Marquee样式进度条指示正在发生的事情。但是,从Microsoft文档here可以看出,可以使用/pipe开关让.NET Framework安装程序报告它的安装进度,这可能会允许它以实际进度交互式更新正常样式进度条。这意味着.NET Framework安装程序可以完全隐藏,Inno Setup用于指示相对进度,这是一个更整洁的解决方案。不幸的是,我不懂C ++,只是一个新手程序员。因此,任何人都可以确认这是否可以与Inno Setup一起使用,如果可以,可以尝试如何进行尝试?

1 个答案:

答案 0 :(得分:6)

以下显示了来自的代码的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 = $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;

您可以像下面一样使用它,也可以在安装程序的任何其他位置使用它。

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;

以下屏幕截图显示&#34;进展页面&#34;在Inno Setup中链接到.NET框架安装程序(当然,/q开关隐藏了.NET框架安装程序,它只是暂时显示以获取屏幕截图)。

enter image description here

我已经成功测试了

上的代码

请注意,代码仅考虑InstallSoFar,因为上述两个安装程序都处于脱机状态。对于在线安装程序,也应考虑DownloadSoFar。实际上即使离线安装人员也会下载一些东西。

ShellExecuteEx代码取自Inno Setup Exec() function Wait for a limited time