安装 "per-user" 如何以及为何设法写入 HKLM?
How and why installation "per-user" manages to write to HKLM?
"Per-user" 安装模式似乎有一些魔法。
我们的应用程序有不使用安装程序的自动更新程序,我想更新 "Add/Remove programs" window 中显示的应用程序版本。我很惊讶安装信息(以及版本)实际上存储在
HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{ProductId}
和
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\{SId}\Products\{ProductId}
所以我的问题是:安装程序如何在不请求提升的情况下设法在那里写入?为什么每个用户安装都在 HKLM 中注册,尤其是第一个入口,看起来与任何特定用户根本不相关?
其他后续但更实际的问题是我如何从代码更新它(当然没有提升)?
Windows 可以写入该卸载位置,因为它是 Windows。安全限制适用于您所做的事情,而不是 Windows 所做的事情。它毕竟是 OS 并且可以做它需要的。如果非提升安装无法在程序和功能中创建条目,那将是相当奇怪的。这些注册表项实际上并不在 MSI 文件中。
每个用户并不是严格意义上的问题。如果需要,可以提升每用户安装,尽管一个常见的约定是不提升每用户安装。如果您的更新程序写入卸载密钥,它将需要提升。
旁注:如果您的自动更新程序正在更新或替换由 Windows 安装程序安装的文件,那么您就违反了规则。更新只能使用基于 MSI 的解决方案来完成。问题是 Windows 知道(例如)您安装的每个版本化文件的文件版本。如果用户进行了修复(或者由于某种原因发生了修复),那么您可能会发现 Windows 想要将文件恢复到最初安装的版本。类似地,如果您进行了补丁或重大升级,那么如果磁盘上的版本与注册版本不匹配,Windows 将不知道文件是否需要更新,因此可能会要求原始 MSI 文件来恢复它们.
Windows 安装程序在多个上下文中运行。有调用它的上下文(通常是受限用户,或者至少是管理员组中用户的非管理员变体)和服务上下文。后者几乎可以完全访问机器,但在模拟原始调用者的同时完成大部分工作。自定义和内置操作可以访问服务上下文(如果 UAC 或早期广告允许),服务始终可以使用它。
这就是它获取访问权限的方式。但为什么它会使用此访问权限在 HKLM 中存储有关产品的信息?虽然您可能熟悉 SharedDllRefCount,但它仅用于与其他解决方案的互操作性。 Windows 安装程序反而会自行跟踪类似或扩展的信息,并且它需要访问它管理的所有安装的所有实例,即使它们是针对每个用户的并且该用户未登录。当用户未登录,无法可靠地访问其 HKCU 密钥,因此它不是 Windows 安装程序使用的可行位置。因此它使用 HKLM。
您可能还想知道为什么它必须有权访问每用户安装的数据。简而言之,因为它无法判断安装是否真的是针对每个用户的。特别是在 Windows 7 和 MSIINSTALLPERUSER
之前,但即使是现在也无法轻易确定安装是否可以写入共享计算机位置。所以我猜他们甚至不会尝试确定这一点。毕竟,既然简单的方法就足够了,为什么还要实现困难或脆弱的功能呢?
那怎么更新呢?通过安装更新的 .msi 文件并让 Windows 安装程序服务为您更新它;这些注册表项是允许 Microsoft 随时更改的实施细节。您应该只通过 Msi* API 调用(例如 MsiEnumProductsEx or MsiConfigureProductEx)读取或写入它们。 (诚然,将后者称为 API 来写入这些值有点牵强,但这是为数不多的有效方法之一。)即使数据存储在 HKCU 中,这部分答案也不会有任何不同.
我使用了以下逻辑来更新版本号,它正确反映在控制面板 -> 添加删除程序中
using (var uninstallSubKey = Registry.CurrentUser.OpenSubKey
("SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",true))
{
if (uninstallSubKey != null)
{
var subKeyNames = uninstallSubKey.GetSubKeyNames();
foreach (var subKeyName in subKeyNames)
{
using (var subKey = uninstallSubKey.OpenSubKey(subKeyName, writable: true))
{
if (subKey?.GetValue("DisplayName") as string == "YOUR_PRODUCT_NAME")
{
subKey.SetValue("DisplayVersion", "YOUR_NEW_VERSION",
RegistryValueKind.String);
break;
}
}
}
}
}
感谢和问候
拉贾塞卡
"Per-user" 安装模式似乎有一些魔法。
我们的应用程序有不使用安装程序的自动更新程序,我想更新 "Add/Remove programs" window 中显示的应用程序版本。我很惊讶安装信息(以及版本)实际上存储在
HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{ProductId}
和
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\{SId}\Products\{ProductId}
所以我的问题是:安装程序如何在不请求提升的情况下设法在那里写入?为什么每个用户安装都在 HKLM 中注册,尤其是第一个入口,看起来与任何特定用户根本不相关?
其他后续但更实际的问题是我如何从代码更新它(当然没有提升)?
Windows 可以写入该卸载位置,因为它是 Windows。安全限制适用于您所做的事情,而不是 Windows 所做的事情。它毕竟是 OS 并且可以做它需要的。如果非提升安装无法在程序和功能中创建条目,那将是相当奇怪的。这些注册表项实际上并不在 MSI 文件中。
每个用户并不是严格意义上的问题。如果需要,可以提升每用户安装,尽管一个常见的约定是不提升每用户安装。如果您的更新程序写入卸载密钥,它将需要提升。
旁注:如果您的自动更新程序正在更新或替换由 Windows 安装程序安装的文件,那么您就违反了规则。更新只能使用基于 MSI 的解决方案来完成。问题是 Windows 知道(例如)您安装的每个版本化文件的文件版本。如果用户进行了修复(或者由于某种原因发生了修复),那么您可能会发现 Windows 想要将文件恢复到最初安装的版本。类似地,如果您进行了补丁或重大升级,那么如果磁盘上的版本与注册版本不匹配,Windows 将不知道文件是否需要更新,因此可能会要求原始 MSI 文件来恢复它们.
Windows 安装程序在多个上下文中运行。有调用它的上下文(通常是受限用户,或者至少是管理员组中用户的非管理员变体)和服务上下文。后者几乎可以完全访问机器,但在模拟原始调用者的同时完成大部分工作。自定义和内置操作可以访问服务上下文(如果 UAC 或早期广告允许),服务始终可以使用它。
这就是它获取访问权限的方式。但为什么它会使用此访问权限在 HKLM 中存储有关产品的信息?虽然您可能熟悉 SharedDllRefCount,但它仅用于与其他解决方案的互操作性。 Windows 安装程序反而会自行跟踪类似或扩展的信息,并且它需要访问它管理的所有安装的所有实例,即使它们是针对每个用户的并且该用户未登录。当用户未登录,无法可靠地访问其 HKCU 密钥,因此它不是 Windows 安装程序使用的可行位置。因此它使用 HKLM。
您可能还想知道为什么它必须有权访问每用户安装的数据。简而言之,因为它无法判断安装是否真的是针对每个用户的。特别是在 Windows 7 和 MSIINSTALLPERUSER
之前,但即使是现在也无法轻易确定安装是否可以写入共享计算机位置。所以我猜他们甚至不会尝试确定这一点。毕竟,既然简单的方法就足够了,为什么还要实现困难或脆弱的功能呢?
那怎么更新呢?通过安装更新的 .msi 文件并让 Windows 安装程序服务为您更新它;这些注册表项是允许 Microsoft 随时更改的实施细节。您应该只通过 Msi* API 调用(例如 MsiEnumProductsEx or MsiConfigureProductEx)读取或写入它们。 (诚然,将后者称为 API 来写入这些值有点牵强,但这是为数不多的有效方法之一。)即使数据存储在 HKCU 中,这部分答案也不会有任何不同.
我使用了以下逻辑来更新版本号,它正确反映在控制面板 -> 添加删除程序中
using (var uninstallSubKey = Registry.CurrentUser.OpenSubKey
("SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",true))
{
if (uninstallSubKey != null)
{
var subKeyNames = uninstallSubKey.GetSubKeyNames();
foreach (var subKeyName in subKeyNames)
{
using (var subKey = uninstallSubKey.OpenSubKey(subKeyName, writable: true))
{
if (subKey?.GetValue("DisplayName") as string == "YOUR_PRODUCT_NAME")
{
subKey.SetValue("DisplayVersion", "YOUR_NEW_VERSION",
RegistryValueKind.String);
break;
}
}
}
}
}
感谢和问候 拉贾塞卡