Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Composite MSI installations

Недавно столкнулись с проблемой - нужно было создать композитную инсталляцию. Т.е. склеить два MSI файла в один инсталляционный файл (MSI или EXE). Оказалось, задачка не из простых.

How to bundle several MSI installations into one EXE file and execute them consecutively

Первоначальной целью было создание EXE-шника, который бы запускал подряд две MSI инсталляции. Для такой цели мог бы подойти архиватор, который поддерживает SFX архивы и возможность настроить запуск приложения после завершения распаковки. Самый распостраненный из них - 7zip. Но есть одна проблема - инсталляций несколько, а задать можно лишь один выполняемый файл. Это ограничение можно обойти несколькими путями:

  1. Написать bat-файл (или использовать любой другой скриптовый язык, который поддерживает Windows), из которого будет произведена нужная последовательность запусков инстялляций.

    В случае bat-файла есть существенный недостаток - консольное окно. В случае скриптового сценария есть вероятность, что они в системе запрещены.

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

    Таким упаковщиком является NSIS. Об этом способе я расскажу подробнее, пожалуй.

NSIS MSI installations bundling

Предполагается, что NSIS уже установлен в системе. Я просто приведу пример файла MyBundle.nsi, который собирает нужный EXE-шник:

# Our installation name
Name 'MyInstallationsBundle'

# Output exe-file name
!ifndef OUTPUT_FILE
   !error "You must define OUTPUT_FILE"
!endif

# Path to the first MSI file
!ifndef INSTALLATION1_MSI
   !error "You must define INSTALLATION1_MSI"
!endif

# Path to the second MSI file
!ifndef INSTALLATION2_MSI
   !error "You must define INSTALLATION2_MSI"
!endif

OutFile "${OUTPUT_FILE}"

# Vista UAC support
RequestExecutionLevel admin
AutoCloseWindow true
# No GUI
SilentInstall silent

Section ""
   # Set current directory to TEMP
   SetOutPath $TEMP
   # Extract INSTALLATION?_MSI to TEMP and rename it to Install?.msi
   File /oname=Install1.msi "${INSTALLATION1_MSI}"
   File /oname=Install2.msi "${INSTALLATION2_MSI}"

   # Execute msiexec
   ExecWait 'msiexec /i "$TEMP\Install1.msi" /qr'
   ExecWait 'msiexec /i "$TEMP\Install2.msi"'
SectionEnd

Для гибкости имя выходного файла и пути к инсталляциям не зашиты в скрипт, а передаются билд машиной.

Командная строка сборки выглядит следующим образом:

nsis.exe /NOCD -DOUTPUT_FILE="MyBundle.exe" -DINSTALLATION1_MSI="MyProduct1Install.msi" -DAUTOUPGRADE_MSI="MyProduct2Install.msi" MyBundle.nsi

How to join several MSI installations into one MSI

Первое, что приходит в голову, - запихнуть один MSI файл в другой и выполнить его запуск через Custom Action. Но, не судьба - A system-wide mutex protects the InstallExecuteSequence. The result of this is that only one installation per system can enter the execute sequence. So you cannot launch other installer packages from the execute sequence. После этого сразу же приходит в голову засунуть этот Custom Action в InstallUISequence, но эта последовательность пропускается в случае тихой инсталляции, без показа пользовательского интерфейса.

Погуглив на тему nested MSI installation я нашел документик How to create a nested .msi package. Оказывается, есть некий Custom action type 7 - Nested installation of a product residing in the installation package. То, что доктор прописал.

Для создания MSI файлов у нас используется WiX, который не поддерживает Custom action type 7. Вероятно это связано со следующим:

Concurrent Installations, also called Nested Installations, is a deprecated feature of the Windows Installer. Applications installed with concurrent installations can eventually fail because they are difficult for customers to service correctly. Do not use concurrent installations to install products that are intended to be released to the public. Concurrent installations can have limited applicability in controlled corporate environments when used to install applications that are not intended for public release. The concurrent installations documentation is provided for package authors that wish to use concurrent installations with applications that are not for public distribution.

Выхода три:

  1. Использовать Install Shield, который поддерживает Custom action type 7.
  2. Написать скрипт, который откроет Installation1.msi и выполнит действия, указанные в документе How to create a nested .msi package.
  3. Использовать первый подход (EXE-шник, который бы запускал подряд две MSI инсталляции) плюс некоторые хитрости. Дальше - о них.

How to run a nested MSI installation through msiexec

Напомню, что A system-wide mutex protects the InstallExecuteSequence. Это значит, что нужно запустить Installation2.msi после завершения Installation1.msi. Сделать это можно многими способами, например через Asynchronous Custom Action. Основная проблема - дождаться освобождения блокировки и только после этого произвести запуск Installation2.msi.

Получается, нужно написать программу (скрипт), которая подождет освобождения мьютекса _MSIExecute и запустит Installation2.msi. Программа может попытаться открыть именованый мьютекс _MSIExecute и подождать его освобождения (не пробовал) либо подождать завершения процесса msiexec. В последнем случае через командную строку нужно передать PID (Process ID) msiexec. Windows Installer поддерживает свойство ClientProcessID, значение которого можно без проблем передать, как аргумент командной строки (использовать конструкцию [ClientProcessID] в командной строке запуска приложения).

5 коммент.:

Анонимный комментирует...

Подскажите, пожалуйста, а как запихать один msi в другой?

Unknown комментирует...

Смысл вопроса непонятен. Если просто запихать - так же, как и любой другой файл. Или застримить в специальную таблицу, где хранятся dll-ки с вызываемыми функциями. Вариантов много, вопрос в цели запихивания?

Анонимный комментирует...

Хм.. нам в продукте нужно инсталлить еще один доп. компонент по выбору пользователя. В последних требованиях пришло, что этот продукт должен удаляться отдельно от основного продукта, т.е. иметь отдельную Uninstall string. Первое, что приходит - отдельный инсталлятор и nested msi. Но много ограничений и неудобств. Причем есть требования - инсталлер не может быть ехе-шником (т.е. может вызываться с пом. MSI API), соответственно нельзя написать враппер и т.д.....
Не подскажете, может есть какие опции добавления сепаратных uninstall-ов для разных компонентов? Если еще через wix можно было бы...
спасибо :)

Unknown комментирует...

Если инсталляция с интерфейсом - то можно добавить Сustom Action в InstallUISequence. Блокирование mutex-а происходит только для InstallExecuteSequence. Этот Сustom Action должен, соответственно, достать MSI из MSI и запустить его.

Если речь идет о компонентах, то для этих целей есть Maintenance Mode. но запись в программах будет одна.

Анонимный комментирует...

When some one searches for his vital thing, thus he/she needs to be available
that in detail, so that thing is maintained over here.
Also see my website - GFI Norte

Отправить комментарий

Copyright 2007-2011 Chabster