简体   繁体   English

如何找到已安装 MSI 文件的升级代码?

[英]How can I find the Upgrade Code for an installed MSI file?

In certain cases the need to retrieve MSI upgrade codes for deployed packages can arise.在某些情况下,可能需要检索已部署包的 MSI 升级代码

Common scenarios:常见场景:

  • I took over someone else's MSI project, and I need to determine what upgrade codes were used for previous versions that are already in the wild.我接手了别人的 MSI 项目,我需要确定哪些升级代码用于已经存在的先前版本。 This is necessary to handle upgrade scenarios.这是处理升级方案所必需的。 I have no archive of releases anywhere .我在任何地方都没有发布档案
  • I accidentally changed the upgrade code for my WiX package several times during development and I need to find all Upgrade Code versions "in the wild".我在开发过程中不小心更改了我的 WiX 包的升级代码几次,我需要在“野外”找到所有升级代码版本。 I was not aware that Upgrade Codes should remain stable between versions .我不知道升级代码应该在版本之间保持稳定

This is a Q/A style question .这是一个Q/A 风格的问题

This question has come up before in various incarnations, but this is not a duplicate .这个问题以前在各种化身中出现过,但这不是重复的 I am posting a way to do it that uses the main MSI automation interface (or strictly speaking WMI).我正在发布一种使用主 MSI 自动化界面(或严格来说是 WMI)的方法。 It should be more reliable than registry based approaches from previous answers.应该比以前答案中基于注册表的方法更可靠 This answers also tries to summarize other retrieval approaches.这个答案还试图总结其他检索方法。

MSI Upgrade Code Retrieval (via PowerShell / WMI) MSI 升级代码检索(通过 PowerShell / WMI)

Uninstalling?卸载? : Via Upgrade Code, Via Product Code, Via Product Name, etc... : Via Upgrade Code, Via Product Code, Via Product Name, etc...

The PowerShell script below should retrieve all related product codes , upgrade codes and product names installed on your machine (table output).下面的PowerShell 脚本应检索您机器上安装的所有相关产品代码升级代码产品名称(表输出)。

Screenshot of output (full script below):输出截图(完整脚本如下):

外壳输出

These are the real, live values directly from the Windows Installer database on the machine in question.这些是直接来自相关机器上的Windows Installer 数据库真实实时值 There is no need for any conversion or interpretation.不需要任何转换或解释。 We are going through the proper APIs.我们正在通过适当的 API。

Technical note!技术说明! : Be aware that checking properties directly in your original MSI file (property table) or WiX source file, may not match actual installed values since properties can be overridden at install time via transforms (more info below) - or property values specified at the command line. :请注意,直接在原始 MSI 文件(属性表)或 WiX 源文件中检查属性可能与实际安装的值不匹配,因为可以在安装时通过转换(更多信息如下)或命令中指定的属性值覆盖属性线。 The moral of the story: retrieve property values directly from the system when you can.这个故事的寓意是:尽可能直接从系统中检索属性值。

Quick disclaimer : In rare cases running the script can trigger a Windows Installer self-repair.快速免责声明:在极少数情况下,运行脚本会触发 Windows Installer 自我修复。 Read more in "disclaimer section" below.在下面的“免责声明部分”中阅读更多内容。 Just a potential nuisance, but read the disclaimer please.只是一个潜在的麻烦,但请阅读免责声明。

As a digression, there is also a one-line PowerShell command which will retrieve product codes and upgrade codes only - without the package name included.作为题外话,还有一个单行 PowerShell 命令,它将仅检索产品代码和升级代码 - 不包括包名称。 This might actually suffice for some users (I would recommend the full script below however).对于某些用户来说,这实际上可能就足够了(不过,我会推荐下面的完整脚本)。 There is a screenshot of the output of this one-liner in a section below.在下面的部分中有此单行输出的屏幕截图。 Note : this command appears a lot faster than the larger script (the "Value" field is the upgrade code).注意:此命令比较大的脚本出现得快得多(“值”字段是升级代码)。 Also note: product codes without associated upgrade codes will not show up as far as I can tell - they will in the larger script:另请注意:据我所知,没有相关升级代码的产品代码不会显示 - 它们将在较大的脚本中显示:

gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value

To run the full PowerShell script below:要运行下面的完整 PowerShell 脚本:

  1. Launch PowerShell ( hold down the Windows key, tap R, release the Windows key, type in "powershell" and press OK or hit enter ).启动 PowerShell按住 Windows 键,点击 R,松开 Windows 键,输入“powershell”并按 OK 或按 Enter 键)。
  2. Copy the script below in its entirety, and then just right click inside the PowerShell window .完整复制下面的脚本,然后在 PowerShell 窗口中右键单击
  3. This should start the script, and it will take quite a while to run .这应该会启动脚本,并且需要很长时间才能运行
  4. Please report any problems.请报告任何问题。 I am no PowerShell expert - I am a deployment specialist not a coder, but the script should do the job.我不是 PowerShell 专家 - 我是一名部署专家而不是编码员,但脚本应该可以完成这项工作。
  5. Performance note : I just get the whole Win32_Product WMI object性能说明:我只是得到了整个Win32_Product WMI 对象
    • Cherry picking properties seemed to actually make it marginally slower (VBScript test).樱桃采摘特性似乎实际上使它稍微变慢了(VBScript 测试)。
    • I guess we need to get all rows anyway, and cherry picking columns is just extra lifting?我想无论如何我们都需要获取所有行,而樱桃采摘列只是额外的提升?
    • For Win32_Property we filter both rows and columns (upgrade code is just one of many row-types).对于Win32_Property,我们过滤行和列(升级代码只是许多行类型之一)。 Be prepared for a slow operation, WMI is very slow.为缓慢的操作做好准备,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

Running on Remote Machines在远程机器上运行

  • It should be relatively easy to extend the script above to run on remote machines, but I am not set up to test it properly at the moment.扩展上面的脚本以在远程机器上运行应该相对容易,但我目前还没有准备好正确测试它。
  • The information below has gotten a bit messy, let me know if it is not understandable or unclear.下面的信息有点乱,如果不明白或不清楚,请告诉我。
  • In a real Windows domain it should (in theory) just be a matter of adding the remote machines to the WMI calls themselves (and loop over a list of machines - see mock-up below).真正的 Windows 域中,它应该(理论上)只是将远程机器添加到 WMI 调用本身(并遍历机器列表 - 请参见下面的模型)。 And crucially: you should use a real domain admin account to run the query .至关重要的是:您应该使用真实的域管理员帐户来运行查询 It is possible that the changes I list below to make WMI work in workgroup environments also could be required for some domains, I don't know (firewall rule and UAC registry tweak).有可能我在下面列出的使 WMI 在工作组环境中工作的更改也可能需要某些域,我不知道(防火墙规则和 UAC 注册表调整)。 I would guess that a real domain admin account should have the privileges and access required though.我猜想一个真正的域管理员帐户应该具有所需的权限和访问权限。
  • Remote connections in WMI are affected by (at least) the Windows Firewall , DCOM settings , CIMOM Settings and User Account Control (UAC) (plus any additional non-Microsoft factors - for instance real firewalls, third party software firewalls, security software of various kinds, etc...). WMI 中的远程连接受(至少) Windows 防火墙DCOM 设置CIMOM 设置用户帐户控制 (UAC) (以及任何其他非 Microsoft 因素的影响 - 例如真实防火墙、第三方软件防火墙、各种安全软件)种类等...)。 Here are some details:以下是一些细节:
  • In non-domain networks (small office, home, etc...) you probably have to add user credentials directly to the WMI calls to make it work.非域网络(小型办公室、家庭等)中,您可能必须将用户凭据直接添加到 WMI 调用中才能使其工作。 And you probably must have "real admin rights" on the machines in question to make the queries run remotely in a home network (workgroup).并且您可能必须在相关机器上拥有“真正的管理员权限”才能使查询在家庭网络(工作组)中远程运行。 I have heard that the built-in Administrator account does not have any UAC issues, but I have never tried it.我听说内置的管理员帐户没有任何 UAC 问题,但我从未尝试过。 In my opinion: don't use this account.在我看来:不要使用这个帐户。
    • In my testing I had to ( 1 ) update the Windows firewall rules and ( 2 ) disable the Remote UAC access token filtering and use a real, local admin account on the remote system.在我的测试中,我必须 ( 1 ) 更新 Windows 防火墙规则和 ( 2 ) 禁用远程 UAC 访问令牌过滤并在远程系统上使用真实的本地管理员帐户。 Note that I don't recommend either of these changes , just reporting what worked for me.请注意,我不推荐这些更改中的任何一个,只是报告对我有用的内容。
    • Change 1 : Windows Firewall, run command (cmd.exe, run as admin): netsh advfirewall firewall set rule group="windows management instrumentation (wmi)" new enable=yes (source - see this link for command line to disable this new rule again if you are just testing. Essentially just set enable=no).更改 1 :Windows 防火墙,运行命令(cmd.exe,以管理员身份运行): netsh advfirewall firewall set rule group="windows management instrumentation (wmi)" new enable=yes- 请参阅此命令行链接以禁用此新如果您只是在测试,请再次使用规则。基本上只需设置 enable=no)。 See the linked source for potentially more restrictive rules that could also work.请参阅链接的来源,了解可能也适用的更具限制性的规则。
    • Change 2 : Disable Remote UAC access token filtering: you need to set the following registry value: HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\ LocalAccountTokenFilterPolicy = 1 ( source - mid page, latter half).更改 2 :禁用远程 UAC 访问令牌过滤:您需要设置以下注册表值: HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\ LocalAccountTokenFilterPolicy = 1来源- 中页,后半部分)。 I set a 32-bit DWORD.我设置了一个 32 位的 DWORD。

With those changes in place on the remote system, I also added user credentials to each call by prompting the user $Cred = Get-Credential .在远程系统上进行这些更改后,我还通过提示用户$Cred = Get-Credential向每个调用添加了用户凭据。 There are also more advanced options for defining the user credentials, as explained here: Pass password into -credential (and here ).还有更多用于定义用户凭据的高级选项,如此处所述: Pass password into -credential (和此处)。 To test run, here is a little test script.为了测试运行,这里有一个小测试脚本。 Copy all lines below, modify the remote machine name and paste into PowerShell by right clicking (you will be prompted for credentials):复制下面的所有行,修改远程机器名称并通过右键单击粘贴到 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

For the large PowerShell script above, the basic additions for remote running on several machines in a Windows domain , could be something like this (I won't update the above script since I can't really test this properly).对于上面的大型 PowerShell 脚本,在Windows 域中的多台机器上远程运行的基本添加可能是这样的(我不会更新上面的脚本,因为我无法真正正确地测试它)。 Remember to update the list of remote computer names at the top of the script and run with a domain admin account:请记住更新脚本顶部的远程计算机名称列表并使用域管理员帐户运行:

# 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)
}

To adapt the same machine loop for a non-domain network you can add credentials to the WMI calls.为非域网络调整相同的机器循环,您可以向 WMI 调用添加凭据。 Something like this (you will be prompted for credentials for each machine - which might be confusing).类似这样的事情(系统会提示您输入每台机器的凭据 - 这可能会令人困惑)。 Remember to update the list of remote computer names at the top of the script and use an account with local admin rights on the target box:请记住更新脚本顶部的远程计算机名称列表,并在目标框上使用具有本地管理员权限的帐户:

# 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) 
}

The real answer ends here .真正的答案到此结束 I believe the above newer script should cover most use-cases, but I will leave the content below as well since it is not obsolete, just probably less efficient than the above script.我相信上面较新的脚本应该涵盖大多数用例,但我也会保留下面的内容,因为它并没有过时,只是效率可能不如上面的脚本。 Reading it will probably be repetitious.阅读它可能会重复。

The scripts below for retrieval of single upgrade codes rather than the whole list, could be of interest if you want to retrieve a single upgrade code from within your own application at run-time.如果您想在运行时从您自己的应用程序中检索单个升级代码,下面用于检索单个升级代码而不是整个列表的脚本可能会很有趣。 I'll leave that older content in.我会留下那些较旧的内容。

Disclaimer : The above script uses WMI, and when you access the class Win32_Product it triggers an integrity check of installed packages .免责声明:上述脚本使用 WMI,当您访问类Win32_Product 时,它会触发对已安装包完整性检查 This is quite slow, and can in very special cases trigger an MSI self-repair.这很慢,并且在非常特殊的情况下会触发 MSI 自我修复。 This is not good if you are heading into an important meeting :-).如果您要参加重要会议,这可不好:-)。 Luckily you should be able to cancel any triggered self-repairs (but your query will probably not complete until you let the repair finish).幸运的是,您应该能够取消任何触发的自我修复(但在您完成修复之前,您的查询可能不会完成)。 Quick context link (for safekeeping).快速上下文链接(用于保管)。

IMHO: don't let this stop you from using WMI - it is just an annoyance.恕我直言:不要让这阻止您使用 WMI - 这只是一个烦恼。 Note: both the PowerShell and VBScript approaches described below use WMI and can trigger this issue as well.注意:下面描述的 PowerShell 和 VBScript 方法都使用 WMI,也可能触发此问题。


Retrieving Upgrade Codes For MSI Files That Are Not Installed检索未安装的 MSI 文件的升级代码

If you need the upgrade code for an MSI package that is not installed on your machine, please read the " Manual Retrieval of Upgrade Codes " section towards the bottom for several options (essentially look in the MSI file itself, or its source file used to compile it).如果您需要未安装在您的机器上的 MSI 软件包的升级代码,请阅读底部的“手动检索升级代码”部分以获取多个选项(主要查看 MSI 文件本身,或其源文件用于编译它)。

It is not safe to get the upgrade code for installed packages from the original MSI install file itself or from the (WiX) sources used to compile the MSI, because upgrade codes can be overridden at install time using transforms (details in text below - transforms are little database fragments applied at install time, see that Symantec link for details).从原始 MSI 安装文件本身或从用于编译 MSI 的 (WiX) 源获取已安装软件包的升级代码是不安全的,因为可以在安装时使用转换覆盖升级代码(详细信息在下面的文本中 - 转换是在安装时应用的小数据库片段,有关详细信息,请参阅 Symantec 链接)。

The programmatic retrieval of upgrade codes relies on WMI , and you can use either PowerShell or VBScript to invoke WMI .升级代码的编程检索依赖于WMI ,您可以使用PowerShellVBScript来调用WMI Both methods are presented below.下面介绍这两种方法。 Essentially the following WMI query is run to retrieve the upgrade code for a specified product code:本质上,运行以下WMI 查询以检索指定产品代码的升级代码:

SELECT * FROM Win32_Property WHERE Property='UpgradeCode' AND ProductCode='{YourProdGuid}'

It is the same query used for both VBScript and PowerShell.它与用于 VBScript 和 PowerShell 的查询相同。 You can also run it as a straight WMI query using a tool such as as WMIExplorer.exe .您还可以使用诸如WMIExplorer.exe类的工具将其作为直接 WMI 查询运行。 A very useful tool - highly recommended.一个非常有用的工具 - 强烈推荐。 I believe this is their site: https://github.com/vinaypamnani/wmie2/releases我相信这是他们的网站: https : //github.com/vinaypamnani/wmie2/releases


Retrieve Single Upgrade Code Via PowerShell / WMI通过 PowerShell / WMI 检索单个升级代码

Rather than outputting a whole table with all product codes and upgrade codes, you can retrieve a single upgrade code for a specified product code.您可以检索指定产品代码的单个升级代码,而不是输出包含所有产品代码和升级代码的整个表。 This is good if you are trying to do the retrieval from inside your own application code (then it is just a standard WMI query and has nothing to do with PowerShell).如果您尝试从自己的应用程序代码内部进行检索,这很好(那么它只是一个标准的 WMI 查询,与 PowerShell 无关)。

Below is the single upgrade code retrieval done via PowerShell (to launch PowerShell: hold down the Windows key, tap R, release the Windows key, type in "powershell" and press OK or hit enter ):下面是通过 PowerShell 完成的单个升级代码检索(启动 PowerShell:按住 Windows 键,点击 R,松开 Windows 键,输入“powershell”并按 OK 或按 Enter 键):

gwmi -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' AND ProductCode='{YourGuid}'" | Format-Table Value

The output should be something like this (maybe a little hard to read, I should have used larger fonts):输出应该是这样的(可能有点难以阅读,我应该使用更大的字体):

使用 PowerShell 检索升级代码 - 带注释

The product code specified in the query above is for " Windows SDK Intellidocs ".上述查询中指定的产品代码用于“ Windows SDK Intellidocs ”。 You must obviously replace it with your own product code guid.您显然必须将其替换为您自己的产品代码 guid。 To find the product code you need to pass in, you can also use a PowerShell query as described here: How can I find the product GUID of an installed MSI setup?要查找您需要传入的产品代码,您还可以使用 PowerShell 查询,如下所述: 如何找到已安装的 MSI 设置的产品 G​​UID?

The returned upgrade code is coming straight from the real Windows Installer registry database.返回的升级代码直接来自真实的 Windows Installer 注册表数据库。 It requires no further processing or interpretation or manual conversion steps .它不需要进一步的处理或解释或手动转换步骤 It will also be correct, even if a transform changed the original upgrade code when the MSI was installed (details on transform issues below).即使在安装 MSI 时转换更改了原始升级代码,它也是正确的(有关转换问题的详细信息如下)。

Update, special notice : Without complicating things unnecessarily, I believe I have found a bug in WMI that is very specific.更新,特别注意:在没有不必要地复杂化的情况下,我相信我在 WMI 中发现了一个非常具体的错误。 When an original MSI has no upgrade code set, and you add one via a transform, then WMI does not seem to report the upgrade code at all.当原始 MSI 没有升级代码集,而您通过转换添加一个时,WMI 似乎根本不报告升级代码。 However: if the original MSI has an upgrade code, and you override it in a transform, WMI reports the transform's upgrade code (which is expected).但是:如果原始 MSI 具有升级代码,并且您在转换中覆盖它,WMI 将报告转换的升级代码(这是预期的)。 I definitely saw this, but will need to verify with one more test package to be sure.我确实看到了这一点,但需要再用一个测试包进行验证才能确定。 The moral of the story : always set an upgrade code in your MSI!故事寓意:始终在您的 MSI 中设置升级代码! Then you avoid the whole issue permanently.然后你永久地避免了整个问题。 And don't auto-generate it - hard code it (read "Manual Retrieval of Upgrade Codes" below for an explanation).并且不要自动生成它 - 对其进行硬编码(阅读下面的“升级代码的手动检索”以获得解释)。


Retrieve Single upgrade code using VBScript / WMI (Legacy Approach)使用 VBScript/WMI 检索单个升级代码(传统方法)

There is nothing wrong with the VBScript solution found below - it even has some benefits over PowerShell - despite VBScript being a legacy technology by now.下面的 VBScript 解决方案没有任何问题——它甚至比 PowerShell 有一些优势——尽管 VBScript 现在是一种传统技术。 The benefits are that it should work on all machines, even when the .NET framework is missing (or locked), and on machines where PowerShell is missing (or locked).好处是它应该适用于所有机器,即使 .NET 框架丢失(或锁定),以及 PowerShell 丢失(或锁定)的机器。 It is a dated, but viable solution that is quite flexible (unless VBScript is also locked, but all modern OS versions fully support VBScript).这是一个过时但可行的解决方案,非常灵活(除非 VBScript 也被锁定,但所有现代操作系统版本都完全支持 VBScript)。

In order to make it as simple as possible to retrieve your upgrade code, I have created a " bare-bone VBScript " which should do the trick.为了尽可能简单地检索升级代码,我创建了一个“准系统 VBScript ”,它应该可以解决问题。 It has not been tested for targeting remote computers, even if WMI should be able to do so by design.它尚未针对远程计算机进行测试,即使 WMI 设计上应该能够这样做。 The script is intended to be run on the system where your mystery MSI with the unknown upgrade code is installed.该脚本旨在在安装了未知升级代码的神秘 MSI 的系统上运行。

This VBScript requires an input product code (input dialog shown when script is run), and it will then proceed to look up the corresponding upgrade code (if any).此 VBScript 需要输入产品代码(运行脚本时显示的输入对话框),然后它将继续查找相应的升级代码(如果有)。 As stated above, to locate the product code for your MSI, you can use this approach: How can I find the product GUID of an installed MSI setup?如上所述,要查找 MSI 的产品代码,您可以使用以下方法: 如何找到已安装 MSI 设置的产品 G​​UID? . . Once you have the product code (guid), you can run this VBScript on the target machine and you should get the upgrade code returned to you in a few seconds.获得产品代码 (guid) 后,您可以在目标计算机上运行此 VBScript,几秒钟后您应该会收到升级代码。 WMI retrieval can be very slow. 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

Retrieving All Upgrade Codes and Product Code on a Machine检索机器上的所有升级代码和产品代码

I should mention that I have a large VBScript which will generate a comprehensive HTML report for all installed MSI packages on the machine it runs on .我应该提到,我有一个大型 VBScript,它将为它运行的机器上所有已安装的 MSI 包生成一个综合的 HTML 报告 This includes all upgrade code and a list of related product codes (product codes that share the same upgrade code).这包括所有升级代码和相关产品代码列表(共享相同升级代码的产品代码)。 However, I am not too happy with the code (I am a deployment specialist, not a coder).但是,我对代码不太满意(我是部署专家,而不是编码员)。 The script is too large, too slow and too untested for use , so I create the bare-bone VBScript found above to do retrieval for a single package only.该脚本太大、太慢且未经测试无法使用,因此我创建了上面找到的基本 VBScript 来仅对单个包进行检索。 This script is much easier to test and modify for your own use.此脚本更易于测试和修改以供您自己使用。 I can provide this large VBScript for testing if of interest.如果有兴趣,我可以提供这个大型 VBScript 进行测试。 It is read-only apart from a single HTML file output to "My Documents".除了单个 HTML 文件输出到“我的文档”之外,它是只读的。 It should be possible to adapt this script for use on remote computers as well.也应该可以修改此脚本以在远程计算机上使用。

There is a one-line PowerShell command to retrieve all product codes and related upgrade codes, but this output fill lack the name of the products.一行 PowerShell 命令可用于检索所有产品代码和相关升级代码,但此输出填充缺少产品名称。 I include it here for completeness:为了完整起见,我将其包含在此处:

gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value

The output will be similar to this (the "Value" field is the upgrade code - product codes without associated upgrade codes will not show up as far as I can tell):输出将与此类似(“值”字段是升级代码 - 据我所知,不会显示没有关联升级代码的产品代码):

输出所有升级代码和产品代码


Manual Retrieval of Upgrade Codes手动检索升级代码

This section list some "manual ways" to retrieve upgrade codes that don't need any coding or command lines.本节列出了一些“手动方式”来检索不需要任何编码或命令行的升级代码。 These manual approaches are not the recommended ones.这些手动方法不是推荐的方法。 I include them only because this attempts to be a " reference answer ".我包括它们只是因为这试图成为“参考答案”。 Several different options should be provided.应该提供几个不同的选项。 My recommendation is to use the PowerShell or VBScript provided above.我的建议是使用上面提供的 PowerShell 或 VBScript。

That being said, upgrade codes should generally never change across versions of your product, so chances are you can try the one you find in the MSI file itself, or in the source used to compile it as described below.话虽如此,升级代码通常不应跨产品版本更改,因此您可以尝试在 MSI 文件本身或用于编译它的源代码中找到的代码,如下所述。 The problem that has already been mentioned several times is that a transform can change upgrade codes at install time, so you need to retrieve the upgrade code programatically if you want to be sure you find the correct one.已经多次提到的问题是转换可以在安装时更改升级代码,因此如果您想确保找到正确的升级代码,则需要以编程方式检索升级代码。 Unless you are trying to get the upgrade code from an MSI that is not installed on your system.除非您尝试从系统上未安装的 MSI 获取升级代码。 Then you just need a MSI file viewer as described below in bullet point 1.然后您只需要一个 MSI 文件查看器,如下面的要点 1 中所述。

A transform is just a database fragment with changes that are applied to the original MSI at install time .转换只是一个数据库片段,其中包含在安装时应用于原始 MSI 的更改 It is a tool mostly used for corporate application packaging to modify installers without modifying MSI files directly.它是一种主要用于企业应用程序打包的工具,用于修改安装程序,而无需直接修改 MSI 文件。 Transforms have the extension .mst .转换的扩展名为.mst Changing the upgrade code via a transform is unusual, but not unheard of - especially for corporate repackaging.通过转换更改升级代码是不寻常的,但并非闻所未闻 - 特别是对于企业重新打包。 In rare cases application packagers may intentionally change the upgrade guid to enable them to deliver their own upgrades to the packages installed (instead of relying on the vendor updates directly).极少数情况下,应用程序打包者可能会有意更改升级 guid,以使他们能够为已安装的软件包提供自己的升级(而不是直接依赖供应商更新)。 Rare, but I have seen it done.很少见,但我已经看到它完成了。 Whether this is a good thing or not is highly debatable.这是否是一件好事,值得商榷。

Easy, manual ways to find MSI upgrade codes:查找 MSI 升级代码的简单手动方法

  1. Though offensively obvious, the easiest way to find the upgrade code is to open the original MSI used to install the product and find the upgrade code in the Property table .虽然很明显,但找到升级代码的最简单方法是打开用于安装产品的原始 MSI ,然后在属性表中找到升级代码 All you need is a tool capable of opening MSI files.您所需要的只是一个能够打开 MSI 文件的工具。 Here are some tools: What installation product to use?这里有一些工具: 要使用什么安装产品? InstallShield, WiX, Wise, Advanced Installer, etc . InstallShield、WiX、Wise、Advanced Installer 等 Your fastest bet is probably Orca if you have Visual Studio installed (search for Orca-x86_en-us.msi and install it - this is Microsoft's own, official MSI viewer and editor), or Super Orca if you don't have Visual Studio installed (follow the above link to find it).如果您安装了 Visual Studio,您最快的选择可能是 Orca(搜索Orca-x86_en-us.msi并安装它 - 这是 Microsoft 自己的官方 MSI 查看器和编辑器),或者如果您没有安装 Visual Studio,则可能是 Super Orca (按照上面的链接找到它)。

  2. If you are a developer using WiX (or any other deployment tool), you can obviously find the upgrade code easily in your WiX source file that you used to compile your MSI (or Installshield source, Advanced Installer source, or whatever deployment tool you are using).如果您是使用 WiX(或任何其他部署工具)的开发人员,您显然可以在用于编译 MSI 的WiX 源文件(或 Installshield 源、Advanced Installer 源或您使用的任何部署工具)中轻松找到升级代码使用)。

    • Let's not fly off the handle here with too much well meant advice that clutters the main issue, but you should obviously hard code the upgrade code in your source, and never auto-generate it !让我们不要在这里用太多善意的建议来混淆主要问题,但是您显然应该在源代码硬编码升级代码,并且永远不要自动生成它
    • Upgrade codes define " families of related products " and should remain stable across releases (versions).升级代码定义了“相关产品系列”,并且应该在不同版本(版本)之间保持稳定。 In most cases it should remain stable across language versions as well.在大多数情况下,它也应该跨语言版本保持稳定。 The exact setup depends on deployment requirements.确切的设置取决于部署要求。
    • If products should be able to exist side-by-side you typically have different upgrade codes for the products that need to co-exist.如果产品应该能够并排存在,您通常对需要共存的产品有不同的升级代码。
    • Rule of thumb : keep upgrade codes stable for as long as possible, whenever possible.经验法则:尽可能长时间保持升级代码稳定。 Change them when requirements absolutely demand it.当需求绝对需要时更改它们。
    • To wrap things up: never use the same upgrade code for different products that have their own " life cycle " and no real relation to each other.总结一下:永远不要对具有自己“生命周期”且彼此没有实际关系的不同产品使用相同的升级代码。 They are not related.他们没有关系。 This is just as important as keeping your upgrade code stable for related products.这与保持相关产品的升级代码稳定一样重要。 Think " life cycle " and " family relation " and " co-existence " requirements.思考“生命周期”与“家庭关系”与“共存”的要求。
    • That was a large digression, back to the issue at hand: finding upgrade codes.这是一个很大的题外话,回到手头的问题:查找升级代码。
  3. Even if you don't have the original MSI, it is even possible to locate the cached MSI from the original install in the %SystemRoot%\\Installer folder.即使您没有原始 MSI,甚至可以在%SystemRoot%\\Installer文件夹中从原始安装中找到缓存的 MSI The MSI files here have a mysterious hex-name, but they are just copies of the original MSI files used to install the different products - cached in a safe place to be available for modify, repair and uninstall operations.这里的 MSI 文件有一个神秘的十六进制名称,但它们只是用于安装不同产品的原始 MSI 文件的副本 - 缓存在安全的地方,可用于修改、修复和卸载操作。 Whatever you do, don't mess around in this folder.不管你做什么,都不要在这个文件夹里乱搞。 Never, ever delete anything .永远,永远不要删除任何东西 You can find the MSI that installed your product by selecting the first MSI file, and checking the Windows Explorer status bar what the product name is for older Windows version.您可以通过选择第一个 MSI 文件并检查 Windows 资源管理器状态栏来找到安装产品的 MSI,旧 Windows 版本的产品名称是什么。 In Windows 10 it seems you can hover over an MSI with the pointer and you get a pop-up with some MSI details.在 Windows 10 中,您似乎可以使用指针将鼠标悬停在 MSI 上,然后您会看到一个包含一些 MSI 详细信息的弹出窗口。 You then just click through the list until you find the right product and open the MSI and find the upgrade code in the Property table .然后您只需单击列表,直到找到正确的产品并打开 MSI 并在属性表中找到升级代码。

  4. Some people use the registry to read the upgrade codes: How can I find the upgrade code for an installed application in C#?有些人使用注册表来读取升级代码: 如何在 C# 中找到已安装应用程序的升级代码? . . In my opinion this is not a good approach, there are better ways - such as just using PowerShell as explained above.在我看来,这不是一个好方法,有更好的方法 - 例如如上所述仅使用 PowerShell。 There is no need for all this conversion and interpretation of packed GUIDs (which is the GUID format used in the Windows Installer registry database).不需要对打包的 GUID (这是 Windows Installer 注册表数据库中使用的 GUID 格式)进行所有这些转换和解释。

That should complete the primary "manual methods" to retrieve an upgrade code quickly.这应该完成主要的“手动方法”以快速检索升级代码。 Just some methods for the arsenal that are sometimes good enough.只是一些有时足够好的武器库方法。 There are probably several more ways that I have forgotten.可能还有几种方法我已经忘记了。

Do prefer the programmatic approaches , but if you are in a rush and working without all your tools available some manual options are good.确实更喜欢程序化方法,但是如果您匆忙工作并且没有可用的所有工具,那么一些手动选项是不错的选择。 However some of these manual methods require more tools than the PowerShell command line (you need an MSI file viewer which is not always available on the box if you are on a "support mission" to someone's machine).然而,其中一些手动方法需要比 PowerShell 命令行更多的工具(如果您对某人的机器执行“支持任务”,则需要一个 MSI 文件查看器,该查看器并不总是在盒子上可用)。 The time has come to use PowerShell (yes, I feel outdated too).是时候使用 PowerShell 了(是的,我也觉得过时了)。

Incidentally, MSI files are essentially stripped down SQL Server databases stored as COM-structured storage files (MS Office file format).顺便说一下,MSI 文件本质上是精简的 SQL Server 数据库,存储为 COM 结构的存储文件(MS Office 文件格式)。 Essentially a file system within a file with storage streams of various types.本质上是文件中的文件系统,具有各种类型的存储流。

If you are stuck on a machine without an MSI viewer, you can query cached MSI databases directly from PowerShell:如果您被困在没有 MSI 查看器的机器上,您可以直接从 PowerShell 查询缓存的 MSI 数据库:

To satisfy your requirements for using WMI directly, or for those times you just need a one-off without Powershell (or need to use .bat or whatever), use wmic:为了满足您直接使用 WMI 的要求,或者在那些时候您只需要一次性使用 Powershell(或需要使用 .bat 或其他文件),请使用 wmic:

    C:\>wmic product list brief
    Caption                                                                                              IdentifyingNumber                       Name                                                                                                 Vendor                          Version
        Sourcetree                                                                                           {1B05DFFD-1DB9-48CD-9265-F3976512A579}  Sourcetree                                                                                           Atlassian                       2.6.10.0
        Microsoft Office Access database engine 2007 (English)                                               {90120000-00D1-0409-0000-0000000FF1CE}  Microsoft Office Access database engine 2007 (English)                                               Microsoft Corporation           12.0.4518.1031
        Office 16 Click-to-Run Extensibility Component                                                       {90160000-008C-0000-0000-0000000FF1CE}  Office 16 Click-to-Run Extensibility Component    

There are multiple formatting and output options.有多种格式和输出选项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM