通过 [代码] 指定注册表卸载键 location/hive

Specify the registry uninstall key location/hive via [Code]

Inno Setup 默认查看 PrivilegesRequired 安装变量,如果设置为 adminpoweruser,安装程序会将卸载注册表项安装到 HKLM .如果设置为 lowest,那么它会将注册表项安装到 HKCU

我需要为用户提供安装 "just me" 或 "everybody" 的选项,并通过将目录 selection 页面替换为收音机 select这两个选项的离子。我现在需要做的也是根据此设置修改注册表安装位置。如果我将应用程序安装到本地用户应用程序数据中,那么在 HKLM 级别注册卸载数据将没有意义,因为其他用户将在程序列表中看到它并且仍然无法卸载或使用它。

编辑: 在查看文档和 Install.pas 的源代码后,我找到了 CreateUninstallRegKey 设置指令,它将禁止 Inno 安装注册表密钥,之后我可以添加自己的注册表项,但这真的是唯一的方法吗?

编辑 #2(标记为重复): 我已经看过这个 Conditional Elevation question (and actually implemented it), and it's not the same as mine. The current elevation state does not alter where Inno Setup actually saves the uninstall registry info (in HKCU or HKLM). If you look at the Inno source code (Install.pas #507) 你会看到 PrivilegesRequired 指令是注册表存储位置的主要因素。如果将其设置为 lowest,安装程序是否被提升并不重要 - 它会将注册表项安装到 HKCU,当所需的行为是 select 一个或另一个基于用户时安装首选项,而不是当前的海拔状态。综上所述,我正在寻找一种解决方案来根据代码变量更改注册表根目录,而不管当前的 PrivilegesRequired 或 Elevation 设置如何。

Inno Setup 6 内置了对 selecting between "Install for all users" and "Install for me only" 的支持。

基本上,您可以简单地设置 PrivilegesRequiredOverridesAllowed:

[Setup]
PrivilegesRequiredOverridesAllowed=commandline dialog


对于 Inno Setup 5:如您所见,逻辑是硬编码的。你无法真正控制它。

最接近的方法是使用未记录(已弃用)PrivilegesRequired=none

使用此值(并借助 Windows 中的安装程序自动检测):

  • 当您使用非特权帐户启动安装程序时,它会在不提示您提升权限的情况下启动。如果您决定在安装过程中需要提升,您可以 restart the installer elevated.
  • 当您使用特权帐户启动安装程序时,它总是会提示您提升权限并且不会启动,如果您拒绝的话。所以安装程序总是以提升的方式运行。同样,如果您决定继续未提升的操作,则必须重新启动安装程序。参见 How to Start a Process Unelevated or maybe Run un-elevated command from an elevated prompt?

这不完全是你想要的,但我认为你不能再接近了。


您当然可以通过以下代码自行复制(移动)HKCUHKLM之间的注册表项:

function MoveHKCUUninstallKeyToHKLM: Boolean;
var
  UninstallKey: string;
  AppId: string;
  I: Integer;
  ValueNames: TArrayOfString;
  ValueName: string;
  ValueStr: string;
  ValueDWord: Cardinal;
begin
  if '{#emit SetupSetting("AppId")}' <> '' then
  begin
    AppId := '{#emit SetupSetting("AppId")}';
  end
    else
  begin
    AppId := '{#emit SetupSetting("AppName")}';
  end;

  Result := False;
  if AppId = '' then
  begin
    Log('Cannot identify AppId');
  end
    else
  begin
    UninstallKey :=
      'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + AppId + '_is1';
    Log(Format(
      'AppId identified as "%s", using uninstall key "%s"', [AppId, UninstallKey]));
    if not RegKeyExists(HKCU, UninstallKey) then
    begin
      Log('HKCU uninstall key not found');
    end
      else
    if RegKeyExists(HKLM, UninstallKey) then
    begin
      Log('HKLM uninstall key exists already');
    end
      else
    begin
      Log('HKCU uninstall key found and HKLM key not exists yet');

      if not RegGetValueNames(HKCU, UninstallKey, ValueNames) then
      begin
        Log('Cannot list uninstall key values');
      end
        else
      begin
        I := 0;
        Result := True;
        while (I < GetArrayLength(ValueNames)) and Result do
        begin
          ValueName := ValueNames[I];
          if RegQueryStringValue(HKCU, UninstallKey, ValueName, ValueStr) then
          begin
            if not RegWriteStringValue(HKLM, UninstallKey, ValueName, ValueStr) then
            begin
              Log(Format('Error moving "%s" string value', [ValueName]));
              Result := False;
            end
              else
            begin
              Log(Format('Moved "%s" string value', [ValueName]));
            end;
          end
            else
          if RegQueryDWordValue(HKCU, UninstallKey, ValueName, ValueDWord) then
          begin
            if not RegWriteDWordValue(HKLM, UninstallKey, ValueName, ValueDWord) then
            begin
              Log(Format('Error moving "%s" dword value', [ValueName]));
              Result := False;
            end
              else
            begin
              Log(Format('Moved "%s" dword value', [ValueName]));
            end;
          end
            else
          begin
            { All uninstall values written by Inno Setup are either string or dword }
            Log(Format('Value "%s" is neither string nor dword', [ValueName]));
            Result := False;
          end;
          Inc(I);
        end;

        if Result then
        begin
          if not RegDeleteKeyIncludingSubkeys(HKCU, UninstallKey) then
          begin
            Log('Error removing HKCU uninstall key');
            Result := False;
          end
            else
          begin
            Log('Removed HKCU uninstall key');
          end;
        end;

        if not Result then
        begin
          if not RegDeleteKeyIncludingSubkeys(HKCU, UninstallKey) then
          begin
            Log('Failed to move uninstall key to HKLM, ' +
                'and also failed to rollback the changes');
          end
            else
          begin
            Log('Failed to move uninstall key to HKLM, rolled back the changes');
          end;
        end;
      end;
    end;
  end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  if CurStep = ssPostInstall then
  begin
    Log('Post install');
    MoveHKCUUninstallKeyToHKLM;
  end;
end;

PrivilegesRequired=none 解决方案不是我想要的。在某些情况下,它仍然会提示提升管理员帐户的权限,而且注册表目标仍未反映用户的选择。

因为我已经在我的 Inno Setup 项目中使用了本机帮助程序 DLL,所以我用 C++ 编写了它,因为我在那里更舒服。我在 CurStepChanged 中调用此方法,其中 CurPage=ssDoneInstall。只需使用 [Setup] AppId 调用此方法,以及是否应在本地安装注册表项。

#include <shlwapi.h>
extern "C" __declspec(dllexport)
bool DetectAndMoveRegKeyW(LPCWSTR app_id, bool install_local)
{
    std::wstring s_app = app_id;
    std::wstring path =
        L"Software\Microsoft\Windows\CurrentVersion\Uninstall\" + s_app + L"_is1";
    LPCWSTR c_path = path.c_str();

    LRESULT res;
    HKEY source = nullptr, subKey = nullptr;

    // try to find source in HKLM
    source = HKEY_LOCAL_MACHINE;
    res = RegOpenKeyExW(source, c_path, 0, KEY_READ, &subKey);
    if (subKey != nullptr)
        RegCloseKey(subKey);

    // try to find source in HKCU
    if (res != ERROR_SUCCESS)
    {
        subKey = nullptr;
        source = HKEY_CURRENT_USER;
        res = RegOpenKeyExW(source, c_path, 0, KEY_READ, &subKey);
        if (subKey != nullptr)
            RegCloseKey(subKey);
    }

    if (res != ERROR_SUCCESS)
        return false; // cant find the registry key

    HKEY dest = install_local ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
    if (source == dest)
        return true; // registry already in the right place


    // copy registry key to correct destination
    HKEY hOldKey;
    HKEY hNewKey;
    bool bResult = false;
    if (RegOpenKeyW(source, c_path, &hOldKey) == 0)
    {
        if (RegCreateKeyW(dest, c_path, &hNewKey) == 0)
        {
            bResult = (SHCopyKeyW(hOldKey, nullptr, hNewKey, 0) == 0);
            RegCloseKey(hNewKey);
        }
        RegCloseKey(hOldKey);

        if (bResult)
        {
            RegDeleteKeyW(source, c_path);
        }
    }

    return bResult;
}

我将此方法导出为 cdecl 而不是 stdcall,这是因为 VC++ 在使用 [=17= 时无论如何都会忽略 C extern 和 mangles 方法名称].您需要在 inno (see inno docs for this) 中将其作为 cdecl 导入。此外,当然这是 Unicode-only 实现,如果您需要 Ansi 版本,它应该足够简单。

重要提示:
此代码不完整,它不考虑 64 位注册表重定向。 Inno-Setup 完全忽略 windows 注册表重定向,并且此代码根本不搜索 64 位注册表,因为 Inno 和它本身在 32 位中是 运行。