MSI 升级代码检索(通过 PowerShell / WMI)
正在卸载?:Via Upgrade Code, Via Product Code, Via Product Name, etc...
下面的 PowerShell 脚本 应检索安装在您的设备上的所有相关 产品代码、升级代码和机器(表输出)。
输出的屏幕截图(完整脚本如下):
这些是直接来自相关机器上的Windows Installer 数据库的真实、实时值。不需要任何转换或解释。我们正在使用适当的 API。
技术说明!:请注意,直接在原始 MSI 文件(属性表)或 WiX 源文件中检查属性可能与实际安装的值不匹配,因为可以在安装时通过 @ 覆盖属性987654323@(更多信息见下文)- 或在命令行中指定的属性值。故事的寓意:尽可能直接从系统中检索属性值。
快速免责声明:在极少数情况下,运行脚本会触发 Windows Installer 自我修复。在“免责声明部分”中阅读更多信息
以下。只是一个潜在的麻烦,但请阅读免责声明。
题外话,还有一个单行 PowerShell 命令,它将仅检索产品代码和升级代码 - 不包括包名称。对于某些用户而言,这实际上可能就足够了(不过,我会推荐下面的完整脚本)。在下面的部分中有此单行输出的屏幕截图。 注意:此命令比较大的脚本出现快很多(“值”字段是升级代码)。另请注意:据我所知,没有相关升级代码的产品代码不会显示 - 它们会出现在较大的脚本中:
gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value
运行下面的完整 PowerShell 脚本:
-
启动 PowerShell(按住 Windows 键,点击 R,松开 Windows 键,输入“powershell”并按 OK 或回车)。
-
完整复制下面的脚本,然后只需在 PowerShell 窗口内右键单击。
- 这应该会启动脚本,并且将需要很长时间才能运行。
- 请报告任何问题。我不是 PowerShell 专家 - 我是部署专家而不是编码员,但脚本应该可以胜任。
-
性能说明:我只得到了整个 Win32_Product WMI 对象
- Cherry Picking 属性似乎实际上使它稍微慢了一点(VBScript 测试)。
- 我想我们无论如何都需要获取所有行,而樱桃采摘列只是额外的提升?
- 对于 Win32_Property,我们过滤行和列(升级代码只是众多行类型之一)。为缓慢的操作做好准备,WMI 非常慢。
$wmipackages = Get-WmiObject -Class win32_product
$wmiproperties = gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'"
$packageinfo = New-Object System.Data.Datatable
[void]$packageinfo.Columns.Add("Name")
[void]$packageinfo.Columns.Add("ProductCode")
[void]$packageinfo.Columns.Add("UpgradeCode")
foreach ($package in $wmipackages)
{
$foundupgradecode = $false # Assume no upgrade code is found
foreach ($property in $wmiproperties) {
if ($package.IdentifyingNumber -eq $property.ProductCode) {
[void]$packageinfo.Rows.Add($package.Name,$package.IdentifyingNumber, $property.Value)
$foundupgradecode = $true
break
}
}
if(-Not ($foundupgradecode)) {
# No upgrade code found, add product code to list
[void]$packageinfo.Rows.Add($package.Name,$package.IdentifyingNumber, "")
}
}
$packageinfo | Sort-Object -Property Name | Format-table ProductCode, UpgradeCode, Name
# Enable the following line to export to CSV (good for annotation). Set full path in quotes
# $packageinfo | Export-Csv "[YourFullWriteablePath]\MsiInfo.csv"
# copy this line as well
在远程机器上运行
- 扩展上面的脚本以在远程机器上运行应该相对容易,但我目前还没有准备好对其进行正确测试。
- 下面的信息有点乱,如果不明白或不清楚,请告诉我。
- 在真正的 Windows 域中,它应该(理论上)只是将远程机器添加到 WMI 调用本身(并遍历机器列表 - 请参见下面的模型) . 至关重要的是:您应该使用真实的域管理员帐户来运行查询。我不知道的某些域也可能需要我在下面列出的使 WMI 在工作组环境中工作的更改(防火墙规则和 UAC 注册表调整)。我猜想一个真正的域管理员帐户应该具有所需的权限和访问权限。
- WMI 中的远程连接受(至少)Windows 防火墙、DCOM 设置、CIMOM 设置 和用户帐户的影响控制 (UAC)(加上任何其他非 Microsoft 因素 - 例如真正的防火墙、第三方软件防火墙、各种安全软件等......)。以下是一些细节:
- 在非域网络(小型办公室、家庭等)中,您可能必须将用户凭据直接添加到 WMI 调用中才能使其正常工作。而且您可能必须在相关机器上拥有“真正的管理员权限”才能使查询在家庭网络(工作组)中远程运行。听说内置Administrator账户没有任何UAC问题,但没试过。在我看来:不要使用这个帐户。
-
在我的测试中我必须 (1) 更新 Windows 防火墙规则并 (2) 禁用远程 UAC 访问令牌过滤并使用真实的, 远程系统上的本地管理员帐户。请注意,我不推荐这些更改中的任何一个,只是报告对我有用的更改。
-
更改 1:Windows 防火墙,运行命令(cmd.exe,以管理员身份运行):
netsh advfirewall firewall set rule group="windows management instrumentation (wmi)" new enable=yes (source - 如果您只是在测试,请参阅此命令行链接以再次禁用此新规则. 本质上只是设置 enable=no)。请参阅链接源,了解可能也适用的更具限制性的规则。
-
更改 2:禁用远程 UAC 访问令牌过滤:您需要设置以下注册表值:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\ LocalAccountTokenFilterPolicy = 1(source - 中间页,后半部分)。我设置了一个 32 位 DWORD。
在远程系统上进行了这些更改后,我还通过提示用户 $Cred = Get-Credential 为每个调用添加了用户凭据。还有更多用于定义用户凭据的高级选项,如下所述:Pass password into -credential(和here)。为了测试运行,这里有一个小测试脚本。复制下面的所有行,修改远程计算机名称并通过右键单击粘贴到 PowerShell(系统将提示您输入凭据):
$Cred = Get-Credential
gwmi -ComputerName RemoteMachineName -credential $Cred -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value
# copy this line too
对于上面的大型 PowerShell 脚本,在 Windows 域 中的多台机器上远程运行的基本添加可能是这样的(我不会更新上面的脚本,因为我不能真正正确地测试这个)。请记住更新脚本顶部的远程计算机名称列表并使用域管理员帐户运行:
# DOMAIN NETWORK: mock-up / pseudo snippet ONLY - lacks testing, provided "as is"
$ArrComputers = "Computer1", "Computer2", "Computer3"
foreach ($Computer in $ArrComputers)
{
# here we modify the WMI calls to add machine name
$wmipackages = Get-WmiObject -Class win32_product -ComputerName $Computer
$wmiproperties = gwmi -ComputerName $Computer -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'"
# the rest of the above, large script here (minus the first 2 WMI lines)
}
要为非域网络调整相同的机器循环,您可以将凭据添加到 WMI 调用。像这样的东西(系统会提示您输入每台机器的凭据 - 这可能会令人困惑)。请记住更新脚本顶部的远程计算机名称列表,并在目标框上使用具有本地管理员权限的帐户:
# WORKGROUP NETWORK: mock-up / pseudo snippet ONLY - lacks testing, provided "as is"
$ArrComputers = "Computer1", "Computer2", "Computer3"
foreach ($Computer in $ArrComputers)
{
$Cred = Get-Credential
# here we modify the WMI calls to add machine name AND credentials
$wmipackages = Get-WmiObject -Class win32_product -ComputerName $Computer -credential $cred
$wmiproperties = gwmi -ComputerName $Computer -credential $cred -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'"
# the rest of the above, large script here (minus the first 2 WMI lines)
}
真正的答案到此结束。我相信上面的较新脚本应该涵盖大多数用例,但我也会将内容留在下面,因为它并没有过时,只是效率可能低于上面的脚本。阅读它可能会重复。
如果您想在运行时从自己的应用程序中检索单个升级代码,下面用于检索单个升级代码而不是整个列表的脚本可能会很有趣。我会把旧的内容留在里面。
免责声明:上述脚本使用 WMI,当您访问类 Win32_Product 时,它会触发
已安装软件包的完整性检查。这很慢,并且可以在
非常特殊的情况会触发 MSI 自我修复。如果你是,这不好
参加重要会议:-)。幸运的是,你应该能够
取消任何触发的自我修复(但您的查询可能不会
完成,直到您让修复完成)。 Quick context link(用于保管)。
恕我直言:不要让这阻止您使用 WMI - 它只是一个
烦恼。注意:下面描述的 PowerShell 和 VBScript 方法都使用 WMI,也可以触发此问题。
检索未安装的 MSI 文件的升级代码
如果您需要计算机上未安装的 MSI 软件包的升级代码,请阅读底部的“手动检索升级代码”部分几个选项(主要是查看 MSI 文件本身,或用于编译它的源文件)。
从原始 MSI 安装文件本身或用于编译 MSI 的 (WiX) 源获取已安装软件包的升级代码是不安全的,因为升级代码可以在安装时使用 transforms 覆盖(详细信息在下面的文本中 - 转换是在安装时应用的小数据库片段,有关详细信息,请参阅 Symantec 链接)。
升级代码的程序化检索依赖于WMI,您可以使用PowerShell或VBScript调用WMI。下面介绍这两种方法。本质上,运行以下 WMI 查询 来检索指定产品代码的升级代码:
SELECT * FROM Win32_Property WHERE Property='UpgradeCode' AND ProductCode='{YourProdGuid}'
这是用于 VBScript 和 PowerShell 的相同查询。您还可以使用诸如WMIExplorer.exe 之类的工具将其作为直接的 WMI 查询运行。一个非常有用的工具 - 强烈推荐。我相信这是他们的网站:https://github.com/vinaypamnani/wmie2/releases
通过 PowerShell / WMI 检索单个升级代码
您可以检索指定产品代码的单个升级代码,而不是输出包含所有产品代码和升级代码的整个表。如果您尝试从自己的应用程序代码中进行检索,这很好(然后它只是一个标准的 WMI 查询,与 PowerShell 无关)。
以下是通过 PowerShell 完成的单个升级代码检索(启动 PowerShell:按住 Windows 键,点击 R,松开 Windows 键,输入“powershell”并按 OK 或回车 ):
gwmi -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' AND ProductCode='{YourGuid}'" | Format-Table Value
输出应该是这样的(可能有点难以阅读,我应该使用更大的字体):
上述查询中指定的产品代码用于“Windows SDK Intellidocs”。您显然必须用您自己的产品代码指南替换它。要查找您需要传入的产品代码,您还可以使用此处所述的 PowerShell 查询:How can I find the product GUID of an installed MSI setup?
返回的升级代码直接来自真正的 Windows Installer 注册表数据库。它不需要进一步的处理或解释或手动转换步骤。这也是正确的,即使在安装 MSI 时转换更改了原始升级代码(以下转换问题的详细信息)。
更新,特别通知:在没有不必要的复杂化的情况下,我相信我在 WMI 中发现了一个非常具体的错误。如果原始 MSI 没有设置升级代码,而您通过转换添加了一个,那么 WMI 似乎根本不会报告升级代码。但是:如果原始 MSI 有升级代码,并且您在转换中覆盖它,WMI 会报告转换的升级代码(这是预期的)。我肯定看到了这一点,但需要再使用一个测试包进行验证才能确定。 故事的寓意:始终在您的 MSI 中设置升级代码!然后你就永久地避免了整个问题。并且不要自动生成它 - 对其进行硬编码(阅读下面的“手动检索升级代码”以获得解释)。
使用 VBScript / WMI(传统方法)检索单一升级代码
下面发现的 VBScript 解决方案没有任何问题 - 它甚至比 PowerShell 有一些优势 - 尽管 VBScript 现在是一项遗留技术。好处是它应该在所有机器上工作,即使 .NET 框架丢失(或锁定),以及 PowerShell 丢失(或锁定)的机器上也是如此。这是一个过时但可行的解决方案,非常灵活(除非 VBScript 也被锁定,但所有现代操作系统版本都完全支持 VBScript)。
为了尽可能简单地检索您的升级代码,我创建了一个“bare-bone VBScript”,它应该可以解决问题。它尚未针对远程计算机进行测试,即使 WMI 在设计上应该能够这样做。该脚本旨在在安装了带有未知升级代码的神秘 MSI 的系统上运行。
此 VBScript 需要输入产品代码(脚本运行时显示的输入对话框),然后它将继续查找相应的升级代码(如果有)。如上所述,要找到您的 MSI 的产品代码,您可以使用以下方法:How can I find the product GUID of an installed MSI setup?。获得产品代码 (guid) 后,您可以在目标机器上运行此 VBScript,您应该会在几秒钟内获得返回给您的升级代码。 WMI 检索可能非常缓慢。
'
' Purpose: Barebone / minimal VBScript implementation to allow retrieval of MSI UpgradeCodes via WMI.
'
' Version: 0.2, September.2017 - Stein Åsmul.
'
' Notes:
'
' - As it stands, this script is intended to be run interactively (WScript).
' - Conversion to run via CScript should be trivial (nothing ever is...)
' - The script will ask the user to provide a valid product GUID for an installed MSI.
' - To find a valid product GUID for your system, perhaps see this SO answer: https://stackoverflow.com/a/29937569/129130
' - The script does not RegEx anything to check for valid GUID format (this is barebone - as terse as possible,
' with as little as possible included that can break).
'
' UPDATE: for information on remote running, check "Running on remote machines" section here:
' https://stackoverflow.com/a/46637095/129130 (firewall and registry change seems to be needed).
strComputer = "."
' Remote connections was NOT tested for this script. In principle you should just add the machine name to "strComputer" above.
' AFAIK you must have "real" admin rights on the box you try to connect to. Many users report intermittent problems running remote WMI.
' Remote connections in WMI are affected by the Windows Firewall, DCOM settings, and User Account Control (UAC).
' - Setting up a Remote WMI Connection: https://msdn.microsoft.com/en-us/library/aa822854(v=vs.85).aspx
' - Connecting to WMI on a Remote Computer: https://msdn.microsoft.com/en-us/library/aa389290(v=vs.85).aspx
' - Perhaps useful: https://social.technet.microsoft.com/Forums/lync/en-US/05205b52-0e43-4ce3-a8b8-58ec4c2edea5/wmi-generic-failure-when-accessing-win32product-remotely?forum=winserverManagement
' - Maybe it is also worth noting that I think WMI queries can be slow enough to trigger timeouts,
' and then you have the old favorite: intermittent bugs.
Set owmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
' User interaction
productcode = InputBox("Please paste or type in the product code for the product whose upgrade code you want " + _
"to retrieve (not case sensitive, a blank product code will abort the script)." + vbNewLine + vbNewLine + _
"Please note that the script can take up to a minute to run due to WMI's slowness.", "UpgradeCode retrieval:")
If productcode = vbCancel Or Trim(productcode) = "" Then
WScript.Quit(0)
End If
' Run WMI call and verify that it completes successfully.
On Error Resume Next
Set upgradecode = owmi.ExecQuery("SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' AND ProductCode='" & productcode & "'")
If (Err.number <> 0) Then
MsgBox "The WMI query failed, this is a critical error - aborting.", vbCritical, "Fatal error."
WScript.Quit(2) ' Following exit code "standard" from MSI SDK automation samples
End If
On Error GoTo 0
' Report results.
Select Case upgradecode.count
Case 0
' We have to provide a separate message for this state, since some packages may not have an UpgradeCode.
' However, the product GUID could also have been misspelled.
MsgBox "No UpgradeCode was found, are you sure you entered the correct product GUID?" & vbNewLine & vbNewLine & _
"Note: It is possible for a product to NOT have an UpgradeCode.", vbInformation, "No UpgradeCode found."
Case 1
' The "default state" - should cover almost all normal packages.
' Only one upgrade code should have been retrieved, and it can be referenced by upgradecode.ItemIndex(0).Value on newer systems
' (Vista and later), but on XP this apparently does not work (never tested by me), for compatibility we use a standard For Each
' enumeration instead. Source: https://stackoverflow.com/questions/2378723/get-first-record-from-wmi-execquery
For Each u in upgradecode
Msgbox "The Upgrade Code is: " & u.Value & vbNewLine & vbNewLine & _
"Just press CTRL + C to copy all text in this dialog (then paste to notepad or similar to extract the GUID).", _
vbInformation, "UpgradeCode found."
' Exit For
Next
Case Else
' Should never get here - let us know if you do get this message.
MsgBox "An error occurred, the query returned more than one result. There can only be one UpgradeCode. " & _
"Please report this error on StackOverflow", vbInformation, "Error while retrieving UpgradeCode."
End Select
检索机器上的所有升级代码和产品代码
我应该提到我有一个很大的 VBScript,它会生成一个
上所有已安装 MSI 软件包的综合 HTML 报告
它运行的机器。这包括所有升级代码和相关列表
产品代码(共享相同升级代码的产品代码)。但是,我对代码不太满意(我是部署专家,不是编码员)。剧本
太大、太慢且未经测试无法使用,所以我创建
上面找到的准系统 VBScript 用于检索单个
只包。这个脚本更容易为您的测试和修改
自己使用。
如果有兴趣,我可以提供这个大的 VBScript 进行测试。除了将单个 HTML 文件输出到“我的文档”之外,它是只读的。 应该可以修改此脚本以在远程计算机上使用。
有一个单行PowerShell命令来检索所有产品代码和相关的升级代码,但是这个输出填充缺少产品的名称。为了完整起见,我将其包含在此处:
gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value
输出将与此类似(“值”字段是升级代码 - 据我所知,没有相关升级代码的产品代码不会显示):
手动检索升级代码
本节列出了一些“手动方式”来检索不需要任何编码或命令行的升级代码。这些手动方法不是推荐的方法。我将它们包括在内只是因为这试图成为“参考答案”。应该提供几个不同的选项。 我的建议是使用上面提供的 PowerShell 或 VBScript。
话虽如此,升级代码通常不应在您的产品版本之间更改,因此您可以尝试在 MSI 文件本身或用于编译它的源中找到的那个,如下所述。已经多次提到的问题是转换可以在安装时更改升级代码,因此如果您想确保找到正确的升级代码,则需要以编程方式检索升级代码。除非您尝试从系统上未安装的 MSI 获取升级代码。然后,您只需要一个 MSI 文件查看器,如下文要点 1 所述。
transform 只是一个数据库片段,其更改会在安装时应用于原始 MSI。它是一个主要用于企业应用程序打包修改安装程序而不直接修改MSI文件的工具。转换的扩展名为 .mst。通过转换更改升级代码是不寻常的,但并非闻所未闻 - 特别是对于企业重新包装。在极少数情况下,应用程序打包者可能会故意更改升级指南,以使他们能够为已安装的包提供自己的升级(而不是直接依赖供应商更新)。很少见,但我已经看到它完成了。这是否是一件好事值得商榷。
查找 MSI 升级代码的简单手动方式:
-
虽然很明显,但找到升级代码的最简单方法是打开用于安装产品的原始 MSI,然后在属性表中找到升级代码。您只需要一个能够打开 MSI 文件的工具。这里有一些工具:What installation product to use? InstallShield, WiX, Wise, Advanced Installer, etc。如果您安装了 Visual Studio,您最快的选择可能是 Orca(搜索 Orca-x86_en-us.msi 并安装它 - 这是 Microsoft 自己的官方 MSI 查看器和编辑器),或者如果您没有安装 Visual Studio(按照上面的操作),则可能是 Super Orca找到它的链接)。
-
如果您是使用 WiX(或任何其他部署工具)的开发人员,您显然可以在您用于编译 MSI(或 Installshield 源,高级安装程序源,或您使用的任何部署工具)。
- 让我们不要在这里提出太多善意的建议,这会使主要问题变得混乱,但您显然应该在源代码中对升级代码进行硬编码,并且永远不要自动生成它!
- 升级代码定义了“相关产品系列”,并且应该在不同版本(版本)中保持稳定。在大多数情况下,它也应该在不同的语言版本中保持稳定。具体设置取决于部署要求。
- 如果产品应该能够并存,您通常需要为需要共存的产品提供不同的升级代码。
-
经验法则:尽可能长时间地保持升级代码稳定。当需求绝对需要时更改它们。
- 总结一下:对于具有自己的“生命周期”且彼此之间没有真正关系的不同产品,切勿使用相同的升级代码。它们不相关。这与保持相关产品的升级代码稳定一样重要。想想“生命周期”和“家庭关系”和“共存”的要求。
- 这是一个很大的题外话,回到手头的问题:查找升级代码。
-
即使您没有原始 MSI,也可以在 %SystemRoot%\Installer 文件夹中从原始安装中找到 缓存的 MSI。这里的 MSI 文件有一个神秘的十六进制名称,但它们只是用于安装不同产品的原始 MSI 文件的副本 - 缓存在安全的地方,可用于修改、修复和卸载操作。 无论你做什么,都不要在这个文件夹中乱七八糟。永远不要删除任何东西。您可以通过选择第一个 MSI 文件并检查 Windows 资源管理器状态栏来查找安装您的产品的 MSI,即旧 Windows 版本的产品名称。在 Windows 10 中,您似乎可以将指针悬停在 MSI 上,然后您会看到一个包含一些 MSI 详细信息的弹出窗口。然后,您只需单击列表直到找到正确的产品并打开 MSI 并在属性表中找到升级代码。
-
有些人使用注册表读取升级代码:How can I find the upgrade code for an installed application in C#?。在我看来,这不是一个好方法,还有更好的方法——比如只使用上面解释的 PowerShell。无需对packed GUIDs(Windows Installer 注册表数据库中使用的 GUID 格式)进行所有这些转换和解释。
这应该完成了快速检索升级代码的主要“手动方法”。只是一些有时足够好的武器库方法。我可能忘记了更多的方法。
更喜欢程序化方法,但如果您赶时间并且在没有所有可用工具的情况下工作,一些手动选项是不错的选择。但是,其中一些手动方法需要比 PowerShell 命令行更多的工具(如果您对某人的机器执行“支持任务”,则您需要一个 MSI 文件查看器,该文件查看器并不总是可用的)。是时候使用 PowerShell(是的,我也觉得过时了)。
顺便说一句,MSI 文件本质上是从 SQL Server 数据库中剥离出来的,存储为 COM 结构的存储文件(MS Office 文件格式)。本质上是一个文件中的文件系统,具有各种类型的存储流。
如果您被困在没有 MSI 查看器的机器上,您可以直接从 PowerShell 查询缓存的 MSI 数据库: