MSI Interop с использованием MSIEnumRelatedProducts и MSIGetProductInfo - PullRequest
1 голос
/ 25 октября 2010

Работая с MSI Interop API, я столкнулся с необычным поведением, которое вызывает сбой моего приложения.Это достаточно просто, чтобы «обработать» проблему, но я хотел бы узнать больше о том, «почему» это происходит.

Мой первый вызов MSIEnumRelatedProducts возвращает значение 0 и правильно устанавливает мой строковый буфер в код продукта,Насколько я понимаю, это произойдет только в том случае, если для данного кода обновления (переданного как метод в метод) в настоящее время установлен «связанный продукт семейства», в противном случае он вернет 259 ERROR_NO_MORE_ITEMS.

Однако при последующем вызове MSIGetProductInfoиспользуя тот же код продукта, я получаю возвращаемое значение 1605: «Это действие действительно только для продуктов, которые установлены в настоящее время.».

У кого-нибудь есть идеи при каких обстоятельствах это может произойти?Он воспроизводится на 100% на 1 машине, но мне еще не удалось выполнить шаги воспроизведения на другой машине.

Все наши продукты построены с использованием свойства Wix "AllUsers = 1", поэтому продукты должны быть установлены для всех пользователей., а не один.

Любые идеи / предложения приветствуются.

Спасибо, Бен

Обновление: Я заметил, что при запуске проблемного MSI-пакетапри ведении журнала отображается следующая строка:

MSI (88:68) [12: 15: 50: 235]: FindRelatedProducts: не удалось прочитать информацию ASSIGNMENTTYPE для продукта '{840C ... и т. д..... 96}.Пропуск ...

Кто-нибудь знает, что это может означать?

Обновление: пример кода.

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);

Ответы [ 2 ]

5 голосов
/ 25 октября 2010

Я полагаю, что вы пытаетесь использовать MsiGetProductInfo , чтобы получить свойство, отличное от описанного в документации.Например, вы можете без проблем получить значение свойства "PackageCode" (INSTALLPROPERTY_PACKAGECODE), но вы не можете получить значение свойства "UpgradeCode" в отношении MsiGetProductInfo иполучите ошибку 1605 (ERROR_UNKNOWN_PRODUCT).

ОБНОВЛЕНО : ОК, теперь я понимаю вашу проблему.Как вы можете найти в интернете есть ошибка в MsiGetProductInfo , поэтому она работает не всегда.Иногда он возвращается обратно 1605 (ERROR_UNKNOWN_PRODUCT) или 1608 (ERROR_UNKNOWN_PROPERTY).В случае, если единственным выходом является получение свойства версии вручную.Я мог воспроизвести проблему, которую вы описали на моем компьютере, с помощью многоязыкового интерфейса пользователя Microsoft Office Outlook 2010 (UpgradeCode = "{00140000-001A-0000-0000-0000000FF1CE}") и написал обходной путь, где я получаю версию продукта из реестра.В примере я получаю информацию только от HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products.Если вы заинтересованы в продуктах, установленных не только для всех пользователей, вы должны изменить программу.Вот код

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

, который выдает на моем компьютере правильный вывод

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'

вместо ошибок в ProductVersion до.

4 голосов
/ 26 октября 2010

Вам следует взглянуть на XML-документ Deployment Tools Foundation установщика Windows.У него очень зрелое взаимодействие MSI (Microsoft.Deployment.WindowsInstaller), которое значительно упростит написание и тестирование этого кода.

Я вижу, что у вас уже есть WiX (надеюсь, v3 +), так что ищите его в C:\ Program Files \ Windows Installer XML v3 \ папка SDK.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...