Inno Setup从.NET Framework 4.5(或更高版本)安装程序获取进度以更新进度栏位置 [英] Inno Setup Get progress from .NET Framework 4.5 (or higher) installer to update progress bar position

查看:181
本文介绍了Inno Setup从.NET Framework 4.5(或更高版本)安装程序获取进度以更新进度栏位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在安装.NET Framework 4.6.2作为PrepareToInstall事件函数中的必备组件,以便可以获取退出代码,设置NeedsReboot状态或在安装失败时中止操作.我的代码在下面,并且一切正常.

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;

目前,我正在使用/q/passive将安装类型设置为无提示或无人值守,以控制.NET Framework安装程序显示的可见GUI的数量,具体取决于Inno Setup的运行方式和使用的方式.选取框样式进度条,指示正在发生某些事情.但是,从Microsoft文档此处 ,似乎可以使用/pipe开关让.NET Framework安装程序报告其安装进度,这可能使它可以交互地更新实际进度的常规样式进度条.这意味着.NET Framework安装程序可能会被完全隐藏,并且Inno Setup用来指示相对进度,这是一个更为整洁的解决方案.不幸的是,我不懂C ++,而且只是一个新手程序员.因此,任何人都可以确认是否可以使用Inno Setup进行这种操作,如果可以,如何进行尝试?

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?

推荐答案

下面显示了来自
的代码的Pascal脚本实现 如何:从.NET Framework 4.5安装程序获取进度

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;


下面的屏幕快照显示了Inno Setup中的进度页"如何链接到.NET Framework安装程序(当然,.c框架安装程序被/q开关隐藏了,只是出于获取目的而临时显示了该窗口)屏幕截图).


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).

我已经在

  • 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)

请注意,仅当上述两个安装程序都处于脱机状态时,该代码才考虑InstallSoFar.对于在线安装程序,也应考虑DownloadSoFar.实际上,甚至离线安装程序有时也会下载一些东西.

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.

ShellExecuteEx代码取自 Inno Setup Exec()函数,请等待一段有限的时间.. /em>

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

这篇关于Inno Setup从.NET Framework 4.5(或更高版本)安装程序获取进度以更新进度栏位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆