Является ли MsiOpenProduct правильным способом считывания свойств с установленного продукта?
Учитывая код продукта MSI, я хочу получить код обновления (среди прочих свойств) из уже установленного продукта. Я попробовал это, вызвав метод MsiOpenProduct, а затем MsiGetProductProperty(). (Сокращенный) пример выглядит так:
MSIHANDLE handle = NULL;
MsiOpenProduct(strProductCode,&handle);
CString strUpgradeCode;
MsiGetProductProperty(handle,_T("UpgradeCode"), strUpgradeCode.GetBuffer(GUID_LENGTH), &dwSize);
strUpgradeCode.ReleaseBuffer();
MsiCloseHandle(handle);
Это дает мне желаемое значение, и, судя по документации MSDN, это кажется правильным способом сделать это:
Функция MsiOpenProduct открывает продукт для использования с функциями доступа к базе данных продуктов. Функция MsiCloseHandle должна вызываться с дескриптором, когда дескриптор больше не нужен.
Однако при вызове MsiOpenProduct() появляется диалоговое окно "Установщик Windows готовит установку...". Вызов MsiCloseHandle() заставляет его снова исчезнуть.
Это заставило меня задуматься:
- Что делает вызов MsiOpenProduct() под капотом? Я не хочу запускать какие-либо действия, я просто хочу прочитать свойства.
- Я не возражаю против всплывающего диалогового окна, так как это только для кода модульного теста, если у него нет побочных эффектов. И поскольку есть много модульных тестов, которые делают это, он все равно должен работать при быстром открытии и закрытии ручек.
- Хотя я наткнулся на метод MsiGetProductInfo, похоже, нет способа получить код обновления. Я прав?
- Является ли MsiOpenProduct правильным способом чтения свойств, таких как код обновления?
3 ответа
MsiOpenProduct должно быть в порядке. Пока вы не выполняете никаких последовательностей или действий, он ничего не будет делать. Если вы хотите заставить диалог замолчать, вы можете осторожно использовать MsiSetInternalUI() или MsiSetExternalUI().
Другой подход, который вы можете использовать, пока ProductCode
а также UpgradeCode
безопасно статичны (т. е. до тех пор, пока они не изменяются преобразованиями), это для определения местоположения базы данных с помощью MsiGetProductInfo () и вызова MsiOpenDatabase () для этого. Разница в том, что MsiOpenProduct () (или аналогично MsiOpenPackage) применяет преобразования, которые использовались во время установки, и подготавливает сеанс, тогда как MsiOpenDatabase () не делает ни того, ни другого.
Здесь вы найдете исчерпывающий ответ с информацией о том, как получить код обновления с помощью Powershell или VBScript и WMI: Как найти код обновления для установленного файла MSI?
Ниже приведен быстрый базовый пример с использованием автоматизации VBScript / COM (API MSI, а не WMI) и подход, обсуждаемый OP (с использованием метода OpenProduct - COM, эквивалентный функции установщика Win32).
Как обсуждалось в моем комментарии выше, я просто добавлю этот небольшой VBScript, чтобы сделать то же самое, что делает OP в C++. Обратите внимание, что к установщику Windows можно получить доступ через WMI (объект Win32_Product), автоматизацию COM и функции установщика Win32 C++.
По какой-то причине UpgradeCode
поскольку пакет, по-видимому, недоступен непосредственно из COM API или Win32 API. Очень странно, тем более, что это входной параметр для таких функций, как Installer.RelatedProducts - в документации не ясно, должен ли быть действительный вызов RelatedProducts(UpgradeCode)
, но, глядя в msi.IDL
ты видишь: StringList* RelatedProducts([in] BSTR UpgradeCode);
Опция WMI работает, но так же OpenProduct
вызов, показанный ниже (который значительно быстрее и выглядит безопасным - насколько я знаю, WMI полностью доступен только для чтения - но небеса знают, что они делают "под капотом". Они раскручивают объект сеанса? Или они читают из базы данных WMI? WMI как-то "чувствует себя безопаснее").
Прелесть описанного ниже метода заключается в том, что он применяет все преобразования, которые были применены к рассматриваемому продукту во время установки. Если вы хотите записать на диск вместо того, чтобы показывать окна сообщений и не можете потрудиться, просматривая документы, вот аналогичный VBScript, который записывает информацию о пакете в текстовый файл рабочего стола: Как я могу найти GUID продукта установленной установки MSI? - совсем немного вниз по странице, просто скопируйте пару строк, и вы свободны от сообщения).
Заметка! Сценарий ниже создаст один файл журнала для каждого открытого MSI, если в системе включена автоматическая регистрация. Пока он существует, скрипт откроет только один MSI, прежде чем он существует (
Exit For
построить).
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"
Я пытался искать в функциях установщика C32 Win32 любой другой способ получения кода UpgradeCode, но я не вижу ничего очевидного. Сеансовый подход должен работать и в C++, но я немного опасаюсь за выпуск дескрипторов и ресурсов. Я недостаточно хорошо обучен на C++, но знаю более чем достаточно, чтобы быть опасным. Огонь в яме. Так далее...
Интересно, OP извлек все пакеты на коробке или только один. Интересно, будут ли проблемы синхронизации и одновременных проблем с объектами сеанса, замеченные с Javascript, также появляться в C++? Я попробую, думаю, когда-нибудь.
Для информации, которую вы хотите, звучит так, как будто вы можете просто позвонить::MsiGetProductInfo().::MsiOpenDatabase() - очень медленная операция, в то время как:: MsiGetProductInfo () - это (IIRC) больше по сравнению с поиском в реестре.