MSI Interop using MSIEnumRelatedProducts and MSIGetProductInfo

后端 未结 2 939
粉色の甜心
粉色の甜心 2021-01-16 06:34

Whilst working with the MSI Interop API I have come across some unusual behaviour which is causing my application to crash. It is simple enough to \'handle\' the problem bu

相关标签:
2条回答
  • 2021-01-16 07:21

    I suppose that you try to use MsiGetProductInfo to get a property other as described in documentation. For example you can get in the way the value of the "PackageCode" property (INSTALLPROPERTY_PACKAGECODE) without any problem, but you can't get the value of the "UpgradeCode" property with respect of MsiGetProductInfo and receive the error 1605 (ERROR_UNKNOWN_PRODUCT).

    UPDATED: OK, now I understand you problem. How you can find in the internet there are a bug in MsiGetProductInfo, so it work not always. Sometime it get back 1605 (ERROR_UNKNOWN_PRODUCT) or 1608 (ERROR_UNKNOWN_PROPERTY) back. In the case as the only workaround is to get the version property manually. I could reproduce the problem which you described on my computer with the Microsoft Office Outlook 2010 MUI (UpgradeCode = "{00140000-001A-0000-0000-0000000FF1CE}") and wrote a workaround where I get the product version from the registry. In the example I get information only from HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products. If you have an interest to products installed not only for all users you have to modify the program. Here is the code

    using System;
    using System.Text;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
    
    namespace EnumInstalledMsiProducts {
        internal static class NativeMethods {
            internal const int MaxGuidChars = 38;
            internal const int NoError = 0;
            internal const int ErrorNoMoreItems = 259;
            internal const int ErrorUnknownProduct = 1605;
            internal const int ErrorUnknownProperty = 1608;
            internal const int ErrorMoreData = 234;
    
            [DllImport ("msi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern int MsiEnumRelatedProducts (string lpUpgradeCode, int dwReserved,
                int iProductIndex, //The zero-based index into the registered products.
                StringBuilder lpProductBuf); // A buffer to receive the product code GUID.
                                             // This buffer must be 39 characters long.
            // The first 38 characters are for the GUID, and the last character is for
            // the terminating null character.
    
            [DllImport ("msi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern Int32 MsiGetProductInfo (string product, string property,
                StringBuilder valueBuf, ref Int32 cchValueBuf);
        }
        class Program {
            static int GetProperty(string productCode, string propertyName, StringBuilder sbBuffer) {
                int len = sbBuffer.Capacity;
                sbBuffer.Length = 0;
                int status = NativeMethods.MsiGetProductInfo (productCode,
                                                              propertyName,
                                                              sbBuffer, ref len);
                if (status == NativeMethods.ErrorMoreData) {
                    len++;
                    sbBuffer.EnsureCapacity (len);
                    status = NativeMethods.MsiGetProductInfo (productCode, propertyName, sbBuffer, ref len);
                }
                if ((status == NativeMethods.ErrorUnknownProduct ||
                     status == NativeMethods.ErrorUnknownProperty)
                    && (String.Compare (propertyName, "ProductVersion", StringComparison.Ordinal) == 0 ||
                        String.Compare (propertyName, "ProductName", StringComparison.Ordinal) == 0)) {
                    // try to get vesrion manually
                    StringBuilder sbKeyName = new StringBuilder ();
                    sbKeyName.Append ("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products\\");
                    Guid guid = new Guid (productCode);
                    byte[] buidAsBytes = guid.ToByteArray ();
                    foreach (byte b in buidAsBytes) {
                        int by = ((b & 0xf) << 4) + ((b & 0xf0) >> 4);  // swap hex digits in the byte
                        sbKeyName.AppendFormat ("{0:X2}", by);
                    }
                    sbKeyName.Append ("\\InstallProperties");
                    RegistryKey key = Registry.LocalMachine.OpenSubKey (sbKeyName.ToString ());
                    if (key != null) {
                        string valueName = "DisplayName";
                        if (String.Compare (propertyName, "ProductVersion", StringComparison.Ordinal) == 0)
                            valueName = "DisplayVersion";
                        string val = key.GetValue (valueName) as string;
                        if (!String.IsNullOrEmpty (val)) {
                            sbBuffer.Length = 0;
                            sbBuffer.Append (val);
                            status = NativeMethods.NoError;
                        }
                    }
                }
    
                return status;
            }
    
            static void Main () {
                string upgradeCode = "{00140000-001A-0000-0000-0000000FF1CE}";
                StringBuilder sbProductCode = new StringBuilder (39);
                StringBuilder sbProductName = new StringBuilder ();
                StringBuilder sbProductVersion = new StringBuilder (1024);
                for (int iProductIndex = 0; ; iProductIndex++) {
                    int iRes = NativeMethods.MsiEnumRelatedProducts (upgradeCode, 0, iProductIndex, sbProductCode);
                    if (iRes != NativeMethods.NoError) {
                        //  NativeMethods.ErrorNoMoreItems=259
                        break;
                    }
                    string productCode = sbProductCode.ToString();
                    int status = GetProperty (productCode, "ProductVersion", sbProductVersion);
                    if (status != NativeMethods.NoError) {
                        Console.WriteLine ("Can't get 'ProductVersion' for {0}", productCode);
                    }
                    status = GetProperty (productCode, "ProductName", sbProductName);
                    if (status != NativeMethods.NoError) {
                        Console.WriteLine ("Can't get 'ProductName' for {0}", productCode);
                    }
    
                    Console.WriteLine ("ProductCode: {0}{3}ProductName:'{1}'{3}ProductVersion:'{2}'{3}",
                                       productCode, sbProductName, sbProductVersion, Environment.NewLine);
                }
            }
        }
    }
    

    which produce on my computer the correct output

    ProductCode: {90140000-001A-0407-0000-0000000FF1CE}
    ProductName:'Microsoft Office Outlook MUI (German) 2010'
    ProductVersion:'14.0.4763.1000'
    
    ProductCode: {90140000-001A-0419-0000-0000000FF1CE}
    ProductName:'Microsoft Office Outlook MUI (Russian) 2010'
    ProductVersion:'14.0.4763.1000'
    

    instead of errors in the ProductVersion before.

    0 讨论(0)
  • 2021-01-16 07:21

    You should look at Windows Installer XML's Deployment Tools Foundation. It has a very mature MSI Interop ( Microsoft.Deployment.WindowsInstaller ) which will make writing and testing this code a lot easier.

    I see you already have WiX ( hopefully v3+ ) so look for it in the C:\Program Files\Windows Installer XML v3\SDK folder.

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