如何让MSI API误认为指定UpgradeCode已安装?
问题1:安装程序查询UpgradeCode的正确位置?
你提到的Win32_Property其实是单个MSI安装包文件的属性表哦,不是系统里记录已安装产品的地方。安装程序要检查A是否已安装,通常会从两个地方查:
- 注册表:这是最常见的路径,64位系统下,64位程序看
HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\{产品的ProductCode},32位程序则用HKLM\Software\Wow6432Node\...的路径。这个路径下的UpgradeCode值就是对应产品的升级代码。 - WMI的
Win32_Product类:这个类是Windows Installer维护的,里面的UpgradeCode属性对应已安装产品的升级代码,但它本质上也是从注册表和MSI数据库读取的,不是独立存储。
你说已经排除了注册表,可以再确认下是不是找对了32/64位的路径,或者安装程序确实用了WMI查询,但优先推荐先尝试注册表Mock,因为实现起来简单靠谱。
问题2:用PowerShell模拟A的UpgradeCode存在的实现方式
直接修改WMI的Win32_Product是行不通的,因为这个类是Windows Installer管的,不让手动创建或修改实例。最靠谱的方法是在注册表中造一个假的已安装产品条目,让B的安装程序误以为A已经装好了。
具体PowerShell脚本步骤:
我给你写了完整脚本,你只需要替换$targetUpgradeCode为你已知的A的UpgradeCode(格式要带大括号的GUID,比如"{12345678-ABCD-1234-ABCD-1234567890AB}")就行:
# 替换成你手头的产品A的UpgradeCode(必须是带大括号的GUID格式) $targetUpgradeCode = "{你的A的UpgradeCode}" # 生成一个随机的ProductCode(模拟已安装产品A的唯一标识,每个产品都要有独有的GUID) $mockProductCode = [guid]::NewGuid().ToString("B") # 根据系统位数选择对应的注册表路径,兼容32/64位程序 if ([Environment]::Is64BitOperatingSystem) { # 64位系统下,同时创建64位和32位路径的条目,确保不管B是32还是64位都能读到 $uninstallPath64 = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$mockProductCode" $uninstallPath32 = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$mockProductCode" # 创建64位路径的注册表项 if (-not (Test-Path $uninstallPath64)) { New-Item -Path $uninstallPath64 -Force | Out-Null } # 创建32位路径的注册表项 if (-not (Test-Path $uninstallPath32)) { New-Item -Path $uninstallPath32 -Force | Out-Null } # 写入必要的注册表值(两个路径都写,避免遗漏) Set-ItemProperty -Path $uninstallPath64 -Name "UpgradeCode" -Value $targetUpgradeCode -Type String Set-ItemProperty -Path $uninstallPath64 -Name "DisplayName" -Value "Mocked Product A" -Type String Set-ItemProperty -Path $uninstallPath64 -Name "InstallDate" -Value (Get-Date -Format "yyyyMMdd") -Type String Set-ItemProperty -Path $uninstallPath32 -Name "UpgradeCode" -Value $targetUpgradeCode -Type String Set-ItemProperty -Path $uninstallPath32 -Name "DisplayName" -Value "Mocked Product A (32-bit)" -Type String Set-ItemProperty -Path $uninstallPath32 -Name "InstallDate" -Value (Get-Date -Format "yyyyMMdd") -Type String } else { # 32位系统只用这个路径 $uninstallPath = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$mockProductCode" if (-not (Test-Path $uninstallPath)) { New-Item -Path $uninstallPath -Force | Out-Null } Set-ItemProperty -Path $uninstallPath -Name "UpgradeCode" -Value $targetUpgradeCode -Type String Set-ItemProperty -Path $uninstallPath -Name "DisplayName" -Value "Mocked Product A" -Type String Set-ItemProperty -Path $uninstallPath -Name "InstallDate" -Value (Get-Date -Format "yyyyMMdd") -Type String } Write-Host "搞定!已经在注册表中Mock了产品A的UpgradeCode,模拟完成安装状态~"
测试后清理(可选):
测试完想删掉这个假条目?运行下面的脚本就行,记得把$mockProductCode替换成之前生成的那个GUID(或者你可以在第一个脚本里把这个GUID保存到文件里,方便后续清理):
$mockProductCode = "之前生成的ProductCode GUID" if ([Environment]::Is64BitOperatingSystem) { $uninstallPath64 = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$mockProductCode" $uninstallPath32 = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$mockProductCode" if (Test-Path $uninstallPath64) { Remove-Item -Path $uninstallPath64 -Recurse -Force } if (Test-Path $uninstallPath32) { Remove-Item -Path $uninstallPath32 -Recurse -Force } } else { $uninstallPath = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$mockProductCode" if (Test-Path $uninstallPath) { Remove-Item -Path $uninstallPath -Recurse -Force } } Write-Host "已清理Mock的产品A注册表条目,系统恢复原样啦~"
为啥这个方法管用?
绝大多数Windows Installer的安装程序在检查依赖时,都会优先读注册表的Uninstall条目,而不是调用复杂的WMI或MSI API。只要注册表中有带目标UpgradeCode的条目,安装程序就会认为对应的产品已经安装,直接跳过依赖检查。
如果真遇到只查WMI的安装程序,没关系——创建注册表条目后,WMI的Win32_Product类下次查询时会自动读取注册表的新内容,不需要额外操作。
内容的提问来源于stack exchange,提问作者PLB

