Поделиться через


Устойчивость приложений: разблокируйте скрытые функции установщика Windows

 

Майкл Сэнфорд
Программное обеспечение 701

Март 2005 г.

Сводка: Установщик Windows имеет несколько функций, которые в значительной степени остались незамеченными сообществом разработчиков. Эти функции позволяют приложению восстанавливать себя во время выполнения или устанавливать дополнительные компоненты в зависимости от взаимодействия пользователя с приложением. (10 печатных страниц)

Скачайте пример интеграции MSI Code.msi.

Содержимое

Введение
Устойчивость за счет интеграции оболочки
Знакомство с API установщика Windows
Ключевые API приложений
Задача 1. Устойчивость Self-Invoked
Задача 2. Установка по запросу
Заключение

Введение

Как разработчики, мы склонны думать о наших приложениях, работающих в идеальных средах, в идеальных системах и идеальных пользователей, которые используют приложение после успешной установки. Реальность такова, что после успешной установки приложений их жизненный цикл для этого пользователя только начался. Проблемы, с которыми может столкнуться наше приложение при оставаться стабильным и функциональным, являются многочисленными, но большинство приложений не готовы к изменениям в операционной среде, которые могут привести к неработоспособной работе приложения.

Установщик Windows предоставляет функции устойчивости, которые добились значительных успехов в поддержании стабильности наших приложений, но эта возможность основана на определенных действиях, выполняемых пользователем при взаимодействии с оболочкой, чтобы предоставить "точки входа", с помощью которых установщик Windows может обнаружить проблемы с конфигурацией приложения и предпринять шаги по его восстановлению.

Ниже приведен краткий список точек входа установщика Windows:

  • Ярлыки. Установщик Windows предоставляет специальный тип ярлыка, который, хотя и является прозрачным для пользователя, содержит дополнительные метаданные, которые установщик Windows использует через интеграцию оболочки для проверки состояния установки указанного приложения перед запуском приложения.
  • Сопоставления файлов. Установщик Windows предоставляет механизм перехвата вызовов связанного с документом или файлом приложения, чтобы при открытии пользователем документа или файла с помощью оболочки установщик Windows смог проверить приложение перед запуском связанного приложения.
  • COM-реклама. Установщик Windows предоставляет механизм, который подключается к подсистеме COM, поэтому любое приложение, создающее экземпляр com-компонента, установленного установщиком Windows (и настроенного для использования этой функции), получит экземпляр этого компонента после того, как установщик Windows проверит состояние установки этого компонента.

В некоторых случаях встроенные функции устойчивости установщика Windows могут не обнаружить все проблемы с конфигурацией приложения, или приложение может работать таким образом, что необходимые точки входа не активируются. К счастью, умные ребята из команды установщика Windows поняли эту проблему и сделали дополнительные функции устойчивости доступными для нас через многофункциональный API установщика Windows.

Устойчивость за счет интеграции оболочки

Прежде чем перейти к расширенным функциям устойчивости, предоставляемым API установщика Windows, давайте рассмотрим типичный сценарий обеспечения устойчивости, который обычно предоставляется бесплатно при развертывании приложений с помощью установщика Windows.

В этом сценарии мы развертываем простое приложение для редактирования текста, которое будет называться SimplePad. Чтобы создать установку, мы будем использовать Microsoft Open Source WiX Toolkit (дополнительные сведения см. в разделе https://sourceforge.net/projects/wix/.), но вы можете сделать то же самое с любым инструментом по вашему выбору.

<Directory Id="TARGETDIR" Name="SourceDir">
<Component Id="SimplePad" Guid="BDDFA5DC-BD69-4232-998E-5167814C21B9" 
  KeyPath="no">
  <File Id="SimplePadConfig" Name="SP.cfg"
    src="$(var.SrcFilesPath)SimplePad.exe.config"
    LongName="SimplePad.exe.config" Vital="yes" KeyPath="no" DiskId="1" />
  <File Id="SimplePad" Name="Simple~1.exe"
    src="$(var.SrcFilesPath)SimplePad.EXE" LongName="SimplePad.exe" Vital="yes"
    KeyPath="yes" DiskId="1" >
  <Shortcut Id="SC1" Advertise="yes"  Directory="ProgramMenuFolder"
    Name="SimpPad" LongName="Run SimplePad"  />
  </File>
</Component>
<Directory Id="ProgramMenuFolder" Name="ProgMenu"></Directory>
</Directory>

Как видно из приведенного выше фрагмента XML, мы создали очень простую установку с одним файлом (SimplePad.exe) и одним ярлыком, расположенным в меню "Пуск" пользователя. Важно отметить, что в этом примере ярлык, который мы создаем, является точкой входа, которую установщик Windows будет использовать для обнаружения состояния приложения и его восстановления по мере необходимости.

На этом этапе можно выполнить сборку установщика, установить приложение и использовать только что созданный контекст меню "Пуск" для его запуска. Как и ожидалось, приложение будет работать точно так, как предполагалось. Чтобы протестировать встроенные функции устойчивости установщика Windows, можно удалить файл SimplePad.exe и попытаться запустить приложение в контекстном меню "Пуск". Опять же, как и ожидалось, установщик Windows обнаруживает, что SimplePad.exe отсутствует, и запускается восстановление. Во время операции восстановления установщик Windows считывает необходимые сведения о конфигурации из внутренне кэшируемой копии пакета установки и, наконец, заменяет отсутствующий файл, запрашивая у пользователя исходный установочный носитель, если он отсутствует в исходном расположении, из которого он был установлен. После завершения операции восстановления приложение запускается в обычном режиме.

Рис. 1. Операция восстановления выполняется

Устойчивость приложений также обеспечивается установщиком Windows с помощью нескольких других механизмов, которые также стоит упомянуть здесь. Вторым наиболее распространенным способом обеспечения высокой доступности приложений является сопоставление файлов установщика Windows. Этот механизм работает так же, как и ярлыки установщика Windows, но вместо прямой связи с исполняемым файлом приложения связь создается зарегистрированным типом файла. Как показано на рисунке 2, сопоставления файлов установщика Windows определяются с помощью того же механизма, что и стандартные сопоставления файлов, за одним исключением. Обратите внимание на рис. 2, что дополнительное значение указано в типичном разделе реестра shell\Open\command. Дополнительное значение (также называемое "command") — это то, где установщик Windows будет выполнять поиск при двойном щелчке файла в оболочке Windows. Эта криптографическая строка, иногда называемая "дескриптором Дарвина", на самом деле представляет собой закодированное представление определенного продукта, компонента и компонента. Если это дополнительное значение существует, установщик Windows декодирует данные и использует их для проверки продукта и компонента. Если обнаруживается, что компонент отсутствует или поврежден, установщик Windows запустит восстановление для восстановления отсутствующих файлов или данных и, наконец, запустит приложение, на которое ссылается ссылка, как обычно, передав ему соответствующие параметры командной строки.

Рис. 2. Просмотр дескриптора Дарвина для сопоставления файлов

Окончательный механизм устойчивости, который мы обсудим сегодня, широко известен как COM-реклама. Прежде чем мы рассмотрим механизм com-рекламы, важно понять, в чем заключается ее вариант использования. Предположим, вы были поставщиком компонентов, который предоставляет общую библиотеку на основе COM, которая обеспечивает почтовые тарифы в реальном времени. Так как этот компонент может использоваться многими различными продуктами, он устанавливается в одном общем расположении в системе конечного пользователя. Чтобы гарантировать, что компонент всегда устанавливается в одном расположении и обеспечивает высокую доступность компонента, вы отправляете его клиентам в модуле слияния, правильно настроенном для использования преимуществ com-рекламы. Конечно, так как ваше решение поставляется в виде одного файла .dll без пользовательского интерфейса, других механизмов устойчивости просто не будет достаточно. В этом случае мы можем полагаться на рекламу COM, чтобы обеспечить правильную установку и регистрацию нашего компонента в системе пользователя. Когда приложение создает экземпляр этого компонента с помощью обычных com-механизмов, установщик Windows "перехватывает" процесс точно так же, как это было с сопоставлениями файлов. Обратите внимание на рис. 3, что на этот раз дескриптор Darwin хранится в значении реестра InprocServer32 для регистрации COM нашего компонента. Опять же, эти сведения декодируются и используются установщиком Windows, чтобы убедиться, что наш компонент правильно установлен и настроен, выполняя все необходимые исправления, прежде чем, наконец, вернуть экземпляр компонента вызывающему приложению.

Стоит отметить, что эта уникальная функция работает полностью независимо от приложения, использующего компонент . Иными словами, даже если приложение, использующее компонент, не было установлено с помощью установщика Windows, com-реклама, используемая компонентом, будет по-прежнему правильно работать, даже если вызывающим приложением является просто VBScript.

Рис. 3. Просмотр дескриптора Darwin для COM-сервера

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

Знакомство с API установщика Windows

Поведение установщика Windows по умолчанию хорошо работало в предыдущем сценарии, но часто в реальном мире у нас есть немного более сложные приложения. Давайте расширим наш пример сценария, чтобы справиться с более сложным сценарием.

Часто приложение состоит из нескольких исполняемых файлов. Одним из примеров может быть приложение, использующее исполняемый файл начального загрузчика для проверка и установки обновлений в приложении, как показано в разделе Блок приложения Updater. В этом случае первым исполняемым файлом является исполняемый файл, который вызывается, когда пользователь щелкает ярлык в меню "Пуск". Он, в свою очередь, запускает второй исполняемый файл, содержащий main пользовательского интерфейса приложения. В зависимости от того, как была настроена установка, есть вероятность того, что проблемы с исполняемым файлом main приложения останутся незамеченными подсистемой установщика Windows. Хотя одним из вариантов может быть написание набора кода, который выполняется при запуске, который проверяет среду выполнения, это просто не будет работать, если сам исполняемый файл отсутствует или поврежден и, кроме того, не сможет легко устранить проблему. Гораздо более эффективным решением является использование знаний установщика Windows о конфигурации приложения, которая уже определена в пакете развертывания.

API установщика Windows предоставляет те же механизмы проверки целостности приложения, которые он использует при взаимодействии пользователя с оболочкой. Используя эти вызовы API из нашего приложения, мы можем быть уверены в том, что по-прежнему получают те же преимущества без использования точек входа оболочки, о которых говорилось ранее.

Ниже приведен список сценариев, не охватываемых функциями устойчивости интеграции оболочки установщика Windows.

  • Приложения, запускаемые с ОС (разделы реестра запуска или однократного запуска)
  • Системные службы
  • Запланированные задачи
  • Приложения, выполняемые другими приложениями
  • Приложения командной строки

Я уверен, что есть еще много сценариев, которые мы могли бы добавить в список выше, но я думаю, что вы получите идею. В следующем примере я продемонстрируем, как можно получить преимущества устойчивости установщика Windows без использования функций интеграции оболочки, которые мы обсуждали ранее. Когда все будет готово, вы сможете принять эти понятия и легко применить их к любому сценарию, в котором выполняется исполняемый код.

Ключевые API приложений

Прежде чем мы рассмотрим некоторые примеры сценариев, давайте рассмотрим некоторые ключевые API установщика Windows, которые можно использовать в приложениях. Конкретные сведения об использовании каждого из этих API см. в справочнике по finction установщика Windows в пакете SDK для платформы.

Ключевые функции установщика Windows Описание
MsiProvideComponent Извлекает расположение установленного компонента, устанавливая или восстанавливая по мере необходимости, чтобы обеспечить доступность компонента.
MsiQueryFeatureState Возвращает состояние установки заданного компонента. Например, эта функция сообщает, установлен ли компонент, а не объявлен.
MsiQueryProductState Возвращает состояние установки продукта. Например, эта функция сообщает, установлен ли продукт, объявлен, установлен для другого пользователя или вообще не установлен.
MsiConfigureProduct
MsiConfigureProductEx
Эти две функции позволяют программно устанавливать или удалять приложение. MsiConfigureProductEx обеспечивает больший контроль, позволяя задавать параметры, аналогичные обычным действиям в командной строке.
MsiConfigureFeature Эта функция позволяет устанавливать, удалять или объявлять определенную функцию приложения.
MsiGetUserInfo Эта функция возвращает имя пользователя, организацию и серийный номер продукта, собранный во время последовательности установки продукта.
MsiGetComponentPath
MsiLocateComponent
Эти две функции помогают определить физическое расположение файла компонента или раздела реестра в целевой системе. MsiGetComponentPath возвращает путь к экземпляру компонента, установленному определенным продуктом, а MsiLocateComponent — первый экземпляр компонента, установленного любым продуктом.

Задача 1. Устойчивость Self-Invoked

Ранее мы говорили о очень простом сценарии, в котором можно удалить исполняемый файл приложения из системы и использовать ярлык, чтобы установщик Windows обнаружил и исправил проблему, переустановив отсутствующий файл. Хотя этот сценарий хорошо работал для демонстрации интеграции оболочки, которую использует установщик Windows, чтобы глубже рассмотреть эти понятия, мы рассмотрим немного более сложный сценарий.

В этом сценарии наше приложение состоит из одного файла .exe и нескольких текстовых файлов, которые предоставляют приложению важные сведения о конфигурации.

Специалисты технической поддержки в нашей гипотетической компании по программному обеспечению получают много запросов на поддержку, которые показывают, что установщик Windows не решает проблемы с конфигурацией приложения из-за того, что пользователи запускают приложение напрямую, дважды щелкая исполняемый файл в Windows Обозреватель вместо использования ярлыка меню "Пуск", созданного при установке.

После консультации с экспертом по развертыванию в штате команды наша команда инженеров решает, что приложение принесет большую пользу, выполняя собственную устойчивость проверка при запуске, чтобы убедиться, что оно правильно настроено. Для этого команда просто добавляет вызов к API MsiProvideComponent , чтобы убедиться, что критически важные компоненты, определенные в пакете установки приложения, правильно установлены и настроены.

<DllImport("msi.dll")> _
Private Shared Function MsiProvideComponent(ByVal szProduct As String, ByVal _
 szFeature As String, ByVal szComponent As String, ByVal dwInstallMode As _
 MSI_REINSTALLMODE, ByVal lpPathBuf As System.Text.StringBuilder, ByRef _
 pcchPathBuf As IntPtr) As Integer
End Function

Public Shared Function ProvideComponent(ByVal productCode As String, ByVal _
 featureName As String, ByVal compID As String) As String
  Dim iRet As Integer
  Dim cbBuffer As Integer = 255
  Dim buffer1 As New System.text.StringBuilder(cbBuffer)
  Dim pSize As New IntPtr(cbBuffer)
  iRet = MsiProvideComponent(productCode, featureName, compID, _
   MSI_INSTALLMODE.INSTALLMODE_DEFAULT, buffer1, pSize)
  Return buffer1.ToString
End Function

Чтобы лучше инкапсулировать этот код, в проект добавляется новый класс с именем WIHelper для размещения объявлений методов API установщика Windows и методов-оболочек. Вызов этого кода был простым добавлением нескольких строк в обработчик событий Load формы main.

Private CONST PRODUCTID As String = "PRODUCT_GUID_HERE"
Private CONST MAIN_FEATUREID As String = "DefaultFeatureKey"
Private CONST COMPID_1 As String = "COMP1_GUID_HERE"
Private CONST COMPID_2 As String = "COMP2_GUID_HERE"
Private Sub MainForm_Load() Handles MyBase.Load
  If WIHelper.IsProductInstalled(PRODUCTID) Then
    WIHelper.ProvideComponent(PRODUCTID, MAIN_FEATUREID, COMPID_1)
    WIHelper.ProvideComponent(PRODUCTID, MAIN_FEATUREID, COMPID_2)
  End If
End Sub
 

В приведенном выше примере кода мы сначала тестируем, чтобы узнать, действительно ли приложение было установлено с помощью пакета установки. Это важная концепция, так как мы хотим убедиться, что даже если выполняется отладка в среде разработки, наше приложение по-прежнему будет работать правильно. Для этого мы вызываем метод во вспомогательном классе с именем IsProductInstalled. Этот метод, в свою очередь, просто вызывает MsiQueryProductState , чтобы определить, установлен ли продукт в системе. Если вызов IsProductInstalled показывает, что продукт установлен, мы делаем ряд вызовов метода ProvideComponent во вспомогательном классе. Этот метод опять же является простой оболочкой для API MsiProvideComponent , которая возвращает полный путь к указанному компоненту и гарантирует, что компонент правильно установлен и готов к использованию. В зависимости от потребностей конкретных продуктов можно вызывать метод ProvideComponent столько раз, сколько угодно, чтобы обеспечить полную доступность приложения для пользователя.

Задача 2. Установка по запросу

Наши гипотетические менеджеры по продажам компании слышали много отзывов от клиентов о том, что они хотели бы видеть набор стандартных шаблонов, предоставляемых с помощью SimplePad. Хотя некоторые клиенты выразили сильное желание использовать эту функцию, другие выразили обеспокоенность по поводу установки лишних данных, которые могут не понадобиться большинству их пользователей.

Подслушав, как инженеры обсуждают, как справиться с этим новым требованием, наш бесстрашный инженер установки вскакивает и указывает, что установщик Windows может легко справиться с этим с помощью небольшого объема дополнительного кода.

После быстрого планирования команда решает, что они будут реализовывать новый пункт меню "Шаблоны" в меню "Файл" приложения. Если компонент шаблонов установлен локально в системе пользователя, пользователь увидит всплывающее меню со списком всех доступных шаблонов и возможность удаления компонента шаблонов. Если компонент шаблонов не установлен, всплывающее меню шаблонов будет содержать одну запись, позволяющую пользователю установить дополнительные шаблоны.

Private Sub mnuFile_Popup(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles mnuFile.Popup
  Dim newItem As MenuItem
  With mnuTemplates.MenuItems
    .Clear()
    If WIHelper.IsFeatureInstalled(PRODUCTID, TEMPLATES_FEATUREID) Then
      Dim dirInfo As New DirectoryInfo(Application.ExecutablePath)
      For Each dirFile As FileInfo In dirInfo.Parent.GetFiles("*.tpl")
        Dim mi As New MenuItem(Path.GetFileNameWithoutExtension(dirFile.Name))
        AddHandler mi.Click, AddressOf OpenTemplate
        .Add(mi)
      Next
      .Add("-")
      newItem = New MenuItem("Uninstall Templates")
      AddHandler newItem.Click, AddressOf UninstallTemplates
      .Add(newItem)
    Else
      newItem = New MenuItem("Install Templates")
      AddHandler newItem.Click, AddressOf InstallTemplates
      .Add(newItem)
    End If
  End With
End Sub

Как видите, сначала проверка, чтобы узнать, установлен ли компонент шаблонов. Если это так, мы перечисляем файлы в папке приложений с расширением "tpl", и добавляем имена каждого шаблона в меню. Если это не так, мы просто добавим параметр для пользователя, чтобы установить шаблоны. Прежде чем мы рассмотрим это, давайте сначала рассмотрим, как определить, установлен ли компонент шаблонов.

<DllImport("msi.dll")> _
Private Shared Function MsiQueryFeatureState(ByVal szProduct As String, 
ByVal szFeature As String) As MSI_INSTALLSTATE
End Function    
Public Shared Function IsFeatureInstalled(ByVal pid As String, ByVal fid As String) As Boolean
  Return MsiQueryFeatureState(pid, fid) = MSI_INSTALLSTATE.INSTALLSTATE_LOCAL
End Function

В этой простой функции мы просто вызываем функцию MsiQueryFeatureState установщика Windows, передавая код продукта приложения и имя функции, о которую мы запрашиваем. Если установщик Windows возвращает INSTALLSTATE_LOCAL возвращается значение true, так как это означает, что компонент установлен локально.

Установка и удаление наших шаблонов выполняется так же просто.

<DllImport("msi.dll")> _
Private Shared Function MsiConfigureFeature(ByVal szProduct As String, ByVal szFeature As String, ByVal eInstallState As MSI_INSTALLSTATE) As Integer
End Function

Public Shared Function InstallFeature(ByVal pid As String, ByVal fid As String)
  As Boolean
  Return MsiConfigureFeature(pid, fid, MSI_INSTALLSTATE.INSTALLSTATE_LOCAL) = ERROR_SUCCESS
End Function

Public Shared Function UninstallFeature(ByVal pid As String, ByVal fid As String) As Boolean
  Return MsiConfigureFeature(pid, fid, 
MSI_INSTALLSTATE.INSTALLSTATE_ABSENT) = ERROR_SUCCESS
End Function

Когда пользователь щелкает пункт меню "Установка шаблонов", выполняется вызов MsiConfigureFeature с productCode, именем компонента, который мы хотим настроить, и значением перечисления, указывающим, что мы хотим установить компонент локально. Во время установки компонента шаблонов пользователь увидит диалоговое окно хода выполнения установщика Windows. Когда диалоговое окно исчезнет, шаблоны будут установлены и готовы к использованию. Когда пользователь возвращается в меню Файл, подменю шаблонов заполняется именами шаблонов, как описано выше.

Заключение

Использование "бесплатных" функций и API, предоставляемых установщиком Windows, предоставляет нам некоторые интересные возможности, которые в значительной степени позволяют сократить затраты на поддержку, повысить стабильность приложений и улучшить взаимодействие с пользователем. Приведенные здесь примеры являются тривиальными по своей природе, но, надеюсь, сформируют отличную отправную точку для реализации собственных уникальных решений. Мы рассмотрели некоторые из доступных API, но мы, конечно, не рассмотрели их все. Уделите некоторое время изучению всех функций API установщика Windows, и я знаю, что вы будете приятно удивлены тем, как легко использовать эти относительно неиспользованные функции установщика Windows.

 

Об авторе

Майкл Сэнфорд ( Michael Sanford ) — президент и главный архитектор программного обеспечения компании 701 Software (http://www.701software.com). До создания 701, Майкл был президентом и генеральным директором корпорации ActiveInstall, которая была приобретена Zero G Software. ActiveInstall достиг известности за свои решения для разработки установщика Windows. Майкл является сертифицированным разработчиком решений Майкрософт (MCSD), сертифицированным системным инженером Майкрософт (MCSE) и MVP установщика Windows. Вы можете прочитать блог Майкла по адресу http://msmvps.com/michael.