MSI Interop using MSIEnumRelatedProducts and MSIGetProductInfo

匿名 (未验证) 提交于 2019-12-03 02:29:01

问题:

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 but I would like to know more about 'why' this is happening.

My first call to MSIEnumRelatedProducts returns an value of 0 and correctly sets my string buffer to a productcode. My understanding is that this would only happen if the given upgradecode (passed as a parm to the method) has a 'related family product' currently installed, otherwise it would return 259 ERROR_NO_MORE_ITEMS.

However when I subsequently call MSIGetProductInfo using the same productcode I get the return value 1605, "This action is only valid for products that are currently installed.".

Does anyone have any ideas under what circumstances this might happen? It is 100% repeatable on 1 machine but I have not yet managed to get reproduction steps on another machine.

All our products are build with the Wix Property "AllUsers=1" so products should be installed for all users, not just one.

Any ideas/suggestions appreciated.

Thanks Ben

Update: I've noticed that when running the problem msi package with logging the following line is shown:

MSI (s) (88:68) [12:15:50:235]: FindRelatedProducts: could not read ASSIGNMENTTYPE info for product '{840C...etc.....96}'. Skipping...

Does anyone have any idea what this might mean?

Update: Code sample.

do {    result = _MSIApi.EnumRelatedProducts(upgradeCode.ToString("B"), 0,                                          productIndex, productCode);    if (result == MSIApi.ERROR_BAD_CONFIGURATION ||        result == MSIApi.ERROR_INVALID_PARAMETER ||        result == MSIApi.ERROR_NOT_ENOUGH_MEMORY)    {       throw new MSIInteropException("Failed to check for related products",                                       new Win32Exception((Int32)result));    }     if(!String.IsNullOrEmpty(productCode.ToString()))    {       Int32 size = 255;       StringBuilder buffer = new StringBuilder(size);       Int32 result = (Int32)_MSIApi.GetProductInfo(productCode,                               MSIApi.INSTALLPROPERTY_VERSIONSTRING,                               buffer,                               ref size);        if (result != MSIApi.ERROR_SUCCESS)       {                         throw new MSIInteropException("Failed to get installed version",                                          new Win32Exception(result));       }        version = new Version(buffer.ToString());    }     productCode = new StringBuilder(39);    productIndex++; } while (result == MSIApi.ERROR_SUCCESS); 

回答1:

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.



回答2:

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.



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!