Inno Setup 需要管理员登录才能在重启后完成

Posted

技术标签:

【中文标题】Inno Setup 需要管理员登录才能在重启后完成【英文标题】:Inno Setup requires Administrator login to complete after restart 【发布时间】:2016-10-22 13:57:02 【问题描述】:

似乎在我能找到的任何地方都没有这方面的信息,我宁愿不要等到它再次发生,然后再开始拼凑。不幸的是,因此,这是一个比我通常想发布的问题更广泛的问题,但由于我还没有确定原因、重现它、收集完整的细节并完整记录它,我只能等到它下一次发生能够收集和提供进一步的信息。所以,根据我所知道的,我希望有人能够对此有所了解。

似乎在某些情况下,Inno Setup 需要重新启动才能完成,这需要管理员在重新启动后登录。我认为这与文件注册有关,可能在System32,由于其他更改已挂起,因此在安装时无法完成。

在发生这种情况的情况下,(我认为)Windows 目录中有三个随机命名的(类似于随机生成的temp 常量)文件,我(回想起来,经过一番思考)假设必须绑定到RunOnce 注册表项(我会在下次看到这种情况时查找),我(再次)假设在以管理员身份登录后运行以完成安装。它们似乎在管理员登录之前不会运行,如果标准用户登录,则安装处于不完整状态。一旦管理员登录,这些文件就会消失并且安装的应用程序按预期工作,而不是给出:

类未注册

在管理员登录并允许安装完全完成之前运行应用程序时出现的错误。

我想要做的是找到一种方法来确保安装完全完成,无论重新启动后登录的用户的权限如何,就好像应用程序是集中部署的(例如通过 SCCM),不会有管理员可用于在 PC 上登录,并且应用程序将不会运行,直到一个人这样做(这违背了使用 SCCM 之类的东西的意义)。实际上,我很惊讶 Inno Setup 没有自动处理这个问题,通过将文件设置为在下次登录时以 SYSTEM 帐户或类似方法运行。

如果有人可以大致解释这里发生了什么,如何找出文件的名称(我可以通过阅读 RunOnce 注册表项来做到这一点,但我需要知道我需要的值的名称阅读)以及运行它们和完成安装需要做什么,我应该能够通过例如使用计划任务在登录时以SYSTEM 运行,或其他一些方法来解决这个问题。

【问题讨论】:

【参考方案1】:

你的假设是正确的。


当 Inno Setup 需要注册一些文件,但它也计算出需要重启机器才能完成一些安装,它会延迟注册直到重启之后。即使需要注册的实际文件已成功安装。


Inno Setup 将创建一个注册表项,如:

[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\RunOnce]
"InnoSetupRegFile.0000000001"="\"C:\\WINDOWS\\is-NGP70.exe\" /REG /REGSVRMODE"

还有像这样的文件:

C:\Windows\is-NGP70.exe
C:\Windows\is-NGP70.lst
C:\Windows\is-NGP70.msg

.lst 文件包含要注册的文件列表:

[s.]C:\Program Files (x86)\My Program\MyClass.dll

注意is-???名称是随机的,与安装程序的临时文件夹不同。

在日志文件中,您将看到:

2016-10-22 18:13:06.439   Delaying registration of all files until the next logon since a restart is needed.
2016-10-22 18:13:06.441   Registration executable created: C:\Users\martin\AppData\Local\Temp\is-NGP70.exe

确实,当安装程序以管理员权限运行时,is-???.exe 将在非管理员登录时静默不做任何事情。

但是如果您以非管理员权限运行安装程序,文件将被写入%TEMP%HKCU\...\RunOnce 的密钥;并且使用/REGU 开关代替/REG 开关;并且任何用户都将继续注册。

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
"InnoSetupRegFile.0000000001"="\"C:\\Users\\user\\AppData\\Local\\Temp\\is-S5KU2.exe\" /REGU /REGSVRMODE"

为了轻松测试场景,只需创建一个简单的安装程序,注册任何.dll 并设置AlwaysRestart=yes

[Setup]
AlwaysRestart=yes

[Files]
Source: "MyClass.dll"; DestDir: "app"; Flags: regserver

如果是因为其他文件而不是需要注册的文件需要重启,可以考虑使用RegisterServer function在Code中注册.dll,避免注册延迟。

RegisterServer(Is64Bit, ExpandConstant('app\MyClass.dll'), False);

使用上述语句的示例,请参见Inno Setup: How to remove Abort from regserver error?


或者,正如您已经建议自己的那样,您可以使用 Window Scheduler 安排具有管理员权限的“onlogon”任务来运行 is-???.exe 文件。

见How to make the program run on startup with admin permission with Inno Setup?

虽然您可以使用 [Run] 部分条目来运行 schtasks(如我对上述问题的回答所示),但该部分仅在创建 RunOnce 条目后才会处理。但是您还需要删除 RunOnce 条目。您不能在[Registry] 部分执行此操作,因为该部分已被处理。您需要在 Pascal 脚本中对此进行编码。那么在代码中两者都做可能会更好。

您可以运行schtasks(使用Exec function)并从CurStepChanged(ssPostInstall) event function 中删除条目(使用RegDeleteValue function)。

您还需要删除该任务。也许您可以使用/Z 开关来“在最终运行后标记要删除的任务”。但我不确定这可以与/SC onlogon 结合使用。如果没有,您需要在任务中运行schtasks /Delete

【讨论】:

我在回答中添加了更多细节。 我可以确认这是由于在 PrepareToInstall 中作为安装的一部分运行 .NET Framework 先决条件安装后将 NeedRestart 函数设置为 True 造成的。至少在这种情况下,解决此问题的最佳方法是将NeedRestart 函数设置为True 移动到安装结束,而不是在开始时。这样做首先可以防止这种情况发生,并避免需要更复杂的解决方案。 但是NeedRestart 在文件注册之前只被调用一次。所以这没有任何意义。 安装程序将在以下情况下重新启动: 1) 它无法替换某些文件,因为它们正在使用中。 2) NeedRestart 返回真。 3) 你使用AlwaysRestart 指令;或带有某些组件或任务的restart 标志 4) 某些Run 程序安排重新启动后文件重命名。 - 所以你几乎可以控制一切,除了文件替换。 如果在您之前执行的其他基于 Inno Setup 的安装程序有待注册,则可以有更多 is-????.exe。我认为您几乎可以放心地假设最高的InnoSetupRegFile.??? 是您的。【参考方案2】:

感谢 Martin 的回答,这是我想出的代码(经过测试和工作)来解决这个问题,以防它帮助其他人。请注意,这有额外的好处(至少对我而言)运行其他产品使用 Inno Setup 进行安装所需的任何和所有挂起的文件注册(需要管理员登录),无论创建它们的安装程序是什么。

procedure CurStepChanged(CurStep: TSetupStep);  
var
  intRegFileNumber, intIndex, intCmdFileRegNumber: Integer;
  strRegFileCmd, strRegFileNumber, strCmdFileRegNumber: String;
  arrRegFileLines: TArrayOfString;
  intResultCode: Integer;
begin
//Run additional tasks after the installation finishes i.e. after the [Run] section completes
  if CurStep = ssPostInstall then
    begin
      //File registrations after restart require Administrator login fix
      if RegValueExists(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InnoSetupRegFile.0000000001') then
        begin
          intRegFileNumber := 1;
          strRegFileNumber := Format('%.10d', [intRegFileNumber]);
          intIndex := 1;
          intCmdFileRegNumber := 0;
          strCmdFileRegNumber := Format('%.3d', [intCmdFileRegNumber]);
          SetArrayLength(arrRegFileLines, 100);
          arrRegFileLines[0] := '@echo off';
          while RegValueExists(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InnoSetupRegFile.' + strRegFileNumber) do
            begin
              RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InnoSetupRegFile.' + strRegFileNumber, strRegFileCmd);
              RegDeleteValue(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InnoSetupRegFile.' + strRegFileNumber);
              arrRegFileLines[intIndex] := strRegFileCmd;
              intRegFileNumber := intRegFileNumber + 1;
              strRegFileNumber := Format('%.10d', [intRegFileNumber]);
              intIndex := intIndex + 1;
            end;
          while FileExists(ExpandConstant('win\is-filereg' + strCmdFileRegNumber + '.cmd')) do
            begin
              intCmdFileRegNumber := intCmdFileRegNumber + 1;
              strCmdFileRegNumber := Format('%.3d', [intCmdFileRegNumber]);
            end;
          arrRegFileLines[intIndex] := 'start cmd.exe /c "timeout.exe /t 2 /nobreak & schtasks.exe /delete /f /tn "Inno Setup File Registrations ' + strCmdFileRegNumber + '" && del /f /q "%windir%\is-filereg' + strCmdFileRegNumber + '.cmd""';
          arrRegFileLines[intIndex + 1] := 'cls';
          arrRegFileLines[intIndex + 2] := 'exit';
          SetArrayLength(arrRegFileLines, intIndex + 3);
          SaveStringsToFile(ExpandConstant('win\is-filereg' + strCmdFileRegNumber + '.cmd'), arrRegFileLines, False);
          Exec(ExpandConstant('sys\schtasks.exe'), '/create /ru "SYSTEM" /sc onstart /rl highest /f /tn "Inno Setup File Registrations ' + strCmdFileRegNumber + '" /tr "''' + ExpandConstant('win\is-filereg' + strCmdFileRegNumber + '.cmd') + '''', '', SW_HIDE,
            ewWaitUntilTerminated, intResultCode);
        end;
    end;
end;

【讨论】:

start cmd.exe 生成一个单独的进程,允许由计划任务启动的进程完成,以便它可以删除计划任务和批处理文件(本身)。有点习惯,我将clsexit 添加到每个批处理文件以确保它关闭。我会看看你建议的其他改变。谢谢。 更新了代码以删除冗余循环并将if更改为while,因为除了检查一个文件之外,这将无法正常工作。将来我会考虑使用TStringList,因为我不太熟悉如何使用这个函数并且它可以正常工作(尽管硬编码的上限为 100,但这应该足够了)。

以上是关于Inno Setup 需要管理员登录才能在重启后完成的主要内容,如果未能解决你的问题,请参考以下文章

Inno Setup检测是否需要预先重启

Inno Setup 安装完成后,默认不重启

Inno Setup Compiler打包需要管理员权限的程序

inno setup 打包使用超级管理员权限

Inno Setup 如何读写文件

仅在需要时使 Inno Setup 安装程序请求权限提升