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

2024-05-20

我目前正在安装 .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 or /passive控制 .NET Framework 安装程序显示的可见 GUI 的数量,具体取决于 Inno Setup 的运行方式并使用字幕样式进度条来指示正在发生的事情。然而,从微软的文档来看here https://msdn.microsoft.com/en-us/library/ff859983%28v=vs.110%29.aspx,似乎可以让 .NET Framework 安装程序报告其安装进度,使用/pipe开关,这可能允许它以交互方式更新实际进度的普通样式进度条。这意味着 .NET Framework 安装程序可以完全隐藏,并且 Inno Setup 用于指示相对进度,这是一个更简洁的解决方案。不幸的是,我不懂C++,只是一个新手程序员。因此,任何人都可以确认这是否可以通过 Inno Setup 来完成,如果可以,如何尝试?


下面显示了代码的 Pascal Script 实现
如何:从 .NET Framework 4.5 安装程序获取进度 https://learn.microsoft.com/en-us/dotnet/framework/deployment/how-to-get-progress-from-the-dotnet-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 '[email protected] /cdn-cgi/l/email-protection stdcall';

function CreateEvent(
  EventAttributes: Cardinal; ManualReset: Boolean; InitialState: Boolean;
  Name: string): THandle;
  external '[email protected] /cdn-cgi/l/email-protection stdcall';

function CreateMutex(
  MutexAttributes: Cardinal; InitialOwner: Boolean; Name: string): THandle;
  external '[email protected] /cdn-cgi/l/email-protection stdcall';

function WaitForSingleObject(
  Handle: THandle; Milliseconds: Cardinal): Cardinal;
  external '[email protected] /cdn-cgi/l/email-protection stdcall';

function MapViewOfFile(
  FileMappingObject: THandle; DesiredAccess: Cardinal; FileOffsetHigh: Cardinal;
  FileOffsetLow: Cardinal; NumberOfBytesToMap: Cardinal): Cardinal;
  external '[email protected] /cdn-cgi/l/email-protection stdcall';

function ReleaseMutex(Mutex: THandle): Boolean;
  external '[email protected] /cdn-cgi/l/email-protection 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 '[email protected] /cdn-cgi/l/email-protection stdcall';

function GetExitCodeProcess(Process: THandle; var ExitCode: Cardinal): Boolean;
  external '[email protected] /cdn-cgi/l/email-protection stdcall';

procedure CopyPointerToData(
  var Destination: TMmioDataStructure; Source: Cardinal; Length: Cardinal);
  external '[email protected] /cdn-cgi/l/email-protection stdcall';

procedure CopyDataToPointer(
  Destination: Cardinal; var Source: TMmioDataStructure; Length: Cardinal);
  external '[email protected] /cdn-cgi/l/email-protection 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;
  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;
          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 Framework 安装程序的(当然 .NET Framework 安装程序被隐藏了)/q开关,它只是为了获取屏幕截图而临时显示的)。


我已经成功测试了代码

  • dotnetfx45_full_x86_x64.exe(.NET Framework 4.5 - 离线安装程序)
  • NDP462-KB3151800-x86-x64-AllOS-ENU.exe(.NET Framework 4.6.2 - 离线安装程序)

请注意,该代码考虑了InstallSoFar仅当上述两个安装程序均离线时。对于在线安装人员来说,DownloadSoFar也应该考虑在内。实际上,即使是离线安装程序有时也会下载一些东西。


The ShellExecuteEx代码取自Inno Setup Exec()函数等待有限时间 https://stackoverflow.com/q/10892452/850848.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Inno Setup 从 .NET Framework 4.5(或更高版本)安装程序获取进度以更新进度栏位置 的相关文章

  • 在 C# 中进行进程间通信 (IPC) 最简单的方法是什么? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我有两个 C 应用程序 我希望其中一个向另一个发送两个整数 这不必很快 因为它每隔几秒调用一次 做到这一点最简单的方法是什么 它不一定是最优雅的
  • 使用PM2时如何配置master进程

    我在 NodeJS 中遇到 PM2 问题 如果没有 PM2 我们总是有如下几行代码来配置主进程 if cluster isMaster master process configuration else worker process con
  • Inno Setup:如何根据代码更改语言文件

    我使用自己的语言文件 isl 而不是使用 Inno Setup 的默认语言文件 它允许我自定义一些消息 但现在 我想要每种语言文件有两个版本 一个用于首次安装 一个用于更新 但是 是否可以从代码中选择一个文件 如果是 该怎么做 也许有一些比
  • Inno Setup:允许用户只选择可以安装软件的驱动器?

    我可以允许用户只选择要安装软件的驱动器吗 例如 他们可以选择C or D drive C Software D Software 但用户不能指定任何其他内容 就像他们不能选择安装下面的软件一样Downloads or MyDocumnets
  • Inno Setup 如何在浏览对话框中显示网络?

    在我的设置中 在浏览器中显示 浏览 按钮 wpSelectDir or CreateInputDirPage例如 网络永远不会显示 我对此进行了一段时间的搜索 但目前还没有找到任何解决方案 有没有办法显示网络并让用户选择网络路径 感谢您对此
  • 登录方式使用GINA定制

    我知道在 GINA 中找到大师并不容易 但我的问题最接近进程间通信 IPC 我用非托管 c 编写了我的自定义 GINA 我在其中包含了一个方法来检查用户尝试的指纹的有效性为了登录 该函数将调用正在运行的系统Windows服务中用C 编写的一
  • Vista幻影目录

    我们有一个程序 安装程序会检查配置文件是否存在 如果存在 它不会复制该文件 它假设用户已修改其配置文件并希望保留这些修改 不幸的是 这是一个 Vista 之前的应用程序 它将配置文件保存在 Program Files 中 问题是 如果你在重
  • inno setup bmp图像出现在单个页面上

    我希望 bmp 图像出现在单个页面 selectadditionaltasks 上 但它出现在所有页面上 我究竟做错了什么 procedure LogoOnClick Sender TObject var ResCode Integer b
  • 欢迎页面未显示,SelectDir 页面首先显示

    我正在尝试使用 Inno Setup 来制作安装程序 我想先显示欢迎页面 然后显示 SelectDir This is CurPageChanged示例代码 procedure CurPageChanged CurPageID intege
  • 有没有办法将消息从 C#.NET 程序集(ActiveX)发送到 VB6 应用程序?

    本问答参考并可用于以下用途 目的 通过ActiveX dll从IE浏览器发送消息到vb6应用程序 从 ActiveX dll 向 vb6 应用程序发送消息 从 C net dll 发送消息到 vb6 应用程序 我读过了本文 http www
  • 如何通过 Inno Setup for NetBeans 使用自定义 .iss 文件

    我将 Inno Setup 5 与 NetBeans 8 一起使用 并且我已经能够创建一个安装程序来安装该应用程序C users username local appname 但是我希望将其安装在C Programfiles 我如何在 Ne
  • 请解释与 Google Chrome 扩展程序的后台通信

    我已经阅读并重新阅读了此页面 并运行了示例 http code google com chrome extensions background pages html http code google com chrome extension
  • 在 Inno Setup 中编译安装程序时跳过不存在的源文件

    In the Files 部分 我必须通过尝试编译一个不存在的文件来实现这一点 编译器不会失败并遵循其过程 例如在这段代码中 Files Source D pais modulo IMG image png DestDir userdocs
  • 是否可以使用 Inno Setup 更改安装程序的窗口标题栏?

    是否可以使用 Inno Setup 更改安装程序的标题栏 默认情况下是 AppName My Program 当您运行安装程序时 标题栏中会出现 设置 我的程序 是否可以隐藏这个词 Setup 将以下行添加到您的 InnoSetup 脚本文
  • 等待进程直到所有子进程完成? [复制]

    这个问题在这里已经有答案了 我有一个创建两个或更多子进程的主进程 我希望主进程等待所有子进程完成其操作并退出 main script py p1 subprocess Popen python script1 py p2 subproces
  • 数据结构的优化存储以实现快速查找和持久化

    Scenario 我有以下方法 public void AddItemSecurity int itemId int userIds public int GetValidItemIds int userId 最初我正在考虑表单上的存储 i
  • 使用 Inno Setup 中格式化(部分粗体)的文本制作安装程序?

    有人看过 GOG com 游戏安装程序吗 如何制作像这样的欢迎文本字符串 包括单个标题中的路径和需要大小 其中部分内容加粗 以下是修改安装路径后如何更改字符串换行的示例 您可以使用TRichEditViewer http www jrsof
  • 在 Inno Setup 中使用 StringToColor

    我想为表单上的标签 TNewStaticText 属性颜色 TColor 读写 分配一些颜色 我将颜色存储为 RRGGBB 字符串 我想使用 Delphi 函数 StringToColor 将其转换为 TColor 但如果我在脚本中使用此函
  • Inno 安装脚本中的 HTTP POST 请求

    我想通过 POST 将 Inno 安装过程中从用户收集的一些信息提交到我们的服务器 明显的解决方案是包含一个 exe 文件 安装程序会将其提取到临时位置并使用参数启动 但是 我想知道 有没有更简单 更好的方法 基于 jsobo 使用建议Wi
  • 如何在“PrepareToInstall”期间显示进度?

    我正在开发的安装程序的大部分工作是在PrepareToInstall功能 因为我需要做的一切都可能失败 因此这是处理这些事情的适当位置 以防它们失败 通过在函数结果中传递错误消息 可以自动报告任何失败 安装程序实际复制的只有 3 个小文件

随机推荐