Is MsiOpenProduct the correct way to read properties from an installed product?

后端 未结 3 1001
有刺的猬
有刺的猬 2020-11-30 14:28

Given an MSI product code I want to get the upgrade code (among other properties) from an already installed product. I have tried this by calling the MsiOpenProduct method,

相关标签:
3条回答
  • 2020-11-30 14:54

    For the information you want, it sounds like you can just call ::MsiGetProductInfo(). ::MsiOpenDatabase() is a very slow operation while ::MsiGetProductInfo() is (IIRC) more on par with registry look ups.

    0 讨论(0)
  • 2020-11-30 15:05

    MsiOpenProduct should be fine So long as you don't run any sequences or actions, it won't do anything. If you want to silence the dialog, you can with careful use of either MsiSetInternalUI() or MsiSetExternalUI().

    Another approach you can take, as long as the ProductCode and UpgradeCode are safely static (i.e. as long as they aren't changed by transforms), is to locate the database using MsiGetProductInfo() and call MsiOpenDatabase() on that. The difference is that MsiOpenProduct() (or similarly MsiOpenPackage) applies the transforms that were used at installation time and prepares a session, whereas MsiOpenDatabase() does neither.

    0 讨论(0)
  • 2020-11-30 15:07

    There is a comprehensive answer with information on how to get the UpgradeCode using Powershell or VBScript and WMI here: How can I find the Upgrade Code for an installed MSI file?

    Below is a quick, basic example using VBScript / COM automation (MSI API, not WMI) and the approach discussed by OP (using the OpenProduct method - the COM equivalent to the Win32 installer function).


    As discussed in my comment above, I will just add this little VBScript to do the same as OP does in C++. Note that Windows Installer can be accessed via WMI (Win32_Product object), COM automation and Win32 C++ installer functions.

    For some reason the UpgradeCode for a package appears to not be available directly from the COM API or the Win32 API. Very strange indeed, especially since it is an input parameter to functions like Installer.RelatedProducts - it is not clear in the documentation that the actual call should be RelatedProducts(UpgradeCode), but looking in the msi.IDL you see: StringList* RelatedProducts([in] BSTR UpgradeCode);

    The WMI option works, but so does this OpenProduct call demonstrated below (which is significantly faster and appears safe - WMI is completely read-only as far as I know though - but heaven knows what they are doing "under the hood". Are they spinning up a session object? Or are they reading from a WMI database? WMI does "feels" safer somehow).

    The beauty of the below method is that it will apply all transforms that were applied to the product in question at installation time. If you want to write to disk instead of showing message boxes and can't be bothered looking up the docs, here is a similar VBScript that writes package info to a desktop text file: How can I find the product GUID of an installed MSI setup? - quite a bit down the page, just copy a couple of lines and you are message box free).

    Note! The script below will create one log file per opened MSI if automatic logging is enabled on the system. As it stands the script will only open one MSI before it exists though (the Exit For construct).

    On Error Resume Next ' This "tersified" script has no error handling
    
    Const msiUILevelNone = 2
    Set installer = CreateObject("WindowsInstaller.Installer")
    Set products = installer.ProductsEx("", "", 7)
    installer.UILevel = msiUILevelNone ' Suppress GUI (MSI progress dialog)
    
    'Iterate over all MSI packages on the box
    For Each product In products
       ' productcode = product.ProductCode
       ' name = product.InstallProperty("ProductName")
       ' version = product.InstallProperty("VersionString")
       ' pkgcode = product.InstallProperty("PackageCode")
    
       Set session = installer.OpenProduct(product.ProductCode)
       upgradecode = session.ProductProperty("UpgradeCode")
       MsgBox upgradecode
       Set session = Nothing ' Important, close the session (doesn't work in Javascript btw)
    
       Exit For   ' End after one iteration (so you don't get a million message boxes)
                  ' Alternatively something like: If i > 4 Then Exit For
    Next
    
    Set installer = Nothing
    
    MsgBox "Finished"
    

    I have tried to look in the C++ Win32 installer functions for any other way to retrieve the UpgradeCode, but I can't see anything obvious. The session approach should work in C++ as well, but I am a little apprehensive about the release of handles and resources. I am not properly potty-trained with C++, but know more than enough to be dangerous. Fire In The Hole. Etc...

    I wonder if the OP retrieved all packages on the box, or just a single one. I wonder if the timing issues and concurrent session object problems seen with Javascript would strike in C++ as well? I will give it a go I think - someday.

    0 讨论(0)
提交回复
热议问题