В статье я хотел бы поделиться своим опытом написания инсталлятора для Windows с использованием инструмента Windows Installer XML Toolset (далее - Wix). К сожалению, несмотря на всю мощь данного инструмента, его использование сильно осложняется куцей документацией, старенькими кукбуками, вялыми ветками форумов и вытеснением .msi и .exe пакетов контейнеризацией. Однако, сегодня продолжают активно развиваться и создаваться программные продукты требующие развертывания на виндовой машине с использованием традицонных установочных пакетов. Прежде, чем мы преступим к написанию инсталлятора нужно решить, что мы будем устанавливать. По большому счету, инструментарий Wix не ограничивает разработчика в технологиях на которых должен быть написан продукт, который будет развернут на машине пользователя. Главное условие, чтобы на машине пользователя была установлена соответствующая версия Windows с пакетами, необходимыми для запуска вашего продукта (кстати, wix позволяет в установочный пакет запихнуть в том числе и необходимые компоненты для настройки окружения, но об этом поговорим позже). Для нашего инсталлятора предлагаю использовать дефолтный проект Blazor: с одной стороны нам не потребуется писать код устанавливаемого проекта, а с другой у нас будет достаточно высокий потенциал для костылизации, чтобы изучить возможности Wix toolset. Прежде чем приступить к практической части предлагаю ускоренный ликбез по технологии Windows installer и wix toolset, что поможет читателю понять мотивы, которыми руководствуются команды разработчиков вставая на путь, вымощенный граблями. Начнем с того, что Wix toolset – это набор инструментов, позволяющий создавать установочные пакеты Windows installer на основе .xml-описания, который включает в себя консольные утилиты, плагин для работы Visual Studio и Rider, несколько тем оформления инсталлятора (глобально они отличаются «глубиной» диалогов, определяющей свободу пользователя в процессе установки), а также средства создания бутстрапера (пожалуй, дословный перевод «установщик» в данном случае не особо применим). Windows installer, в свою очередь, представляет собой подсистему Windows для развертывания программного обеспечения с использованием установочных пакетов .msi (Microsoft Installer). Не погружаясь в историю создания и эволюции данной подсистемы (кому интересно, можете поискать информацию о инсталляторе с кодовым названием Darwin) предлагаю сразу перейти к ее функционалу.  Так, ключевую ценность данной подсистемы представляет высокая гибкость процесса установки, обновления и удаления программ, что обеспечивается: Возможностью отката на любом этапе установки включая апгрейд и удаление софта Предоставлением доступа к работе с реестрами включая чтение и удаление записей Гибкостью работы с правами доступа при установке софта Обеспечением возможности кастомизации интерфейса установки, включая возможность добавления рекламы Предоставлением доступа к ресурсам командной строки и возможности запуска скриптов на разных этапах работы инсталлятора Наличием специализированного ПО, предоставляемого Microsoft для устранения проблем, возникших после запуска инсталляционного пакета с уязвимостью (например, если ошибка приводит к невозможности удаления установленного софта) Сам инсталлятор представляет собой файл с расширением .msi внутри которого располагается реляционная база данных, описывающая логику установки программного обеспечения, а также хранит данные, необходимые для развертывания приложения. Для работы с инсталляционными пакетами вам потребуется установить Windows SDK. Основным инструментом для взаимодействия с файлами .msi является Orca – программа, позволяющая открывать, редактировать и создавать БД инсталляционных пакетов. Для ее установки вам потребуется перейти в директорию C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x86 после установки Windows SDK.  Давайте посмотрим на пример содержимого инсталляционного пакета, который мы с вами создадим с помощью WIX для проекта Blazor. Его содержимое выглядит так: В приведенном примере инсталлятор содержит 35 таблиц. Это число не является фиксированным. Только Orca предлагает включение до 97 шаблонов таблиц, помимо которых можно создавать пользовательские. Разберем по пунктам: в колонке File хранится ID файла, который будет установлен в систему. Он выполняет роль первичного ключа для доступа и связывает сущности из таблицы MsiFileHash и Component    В Component_ лежит значение соответствующее полю KeyPath из таблицы Component, которое служит для реализации функционала, связанного с восстановлением и обновлением ПО средствами WindowsInstaller В FileName хранится непосредственно имя файла FileSize, Version и Language берутся из метаданных устанавливаемого файла Atributes – свойства устанавливаемого файла, определяющие его доступность для пользователей, а также поведение при обновлении, восстановлении и удалении продукта. Подробнее можно ознакомиться по ссылке Sequence определяет положение файла в инсталляторе и должен соответствовать его номеру в cabinet-файле. Если, вы еще не сильно утомились сухой теорией, то давайте посмотрим еще на одну ключевую таблицу – Component: В этой таблице описаны компоненты:  .  В колонке Component лежит название компонента, которое должно быть уникальным. Оно может быть создано как разработчиком, так и средствами автоматизации. Главное, что с помощью этого значения компонент связывается с файлом, который будет в него завернут. ComponentId – GUID, который должен быть сгенерирован для каждого компонента. Фактически это первичный ключ в таблице компонентов.  Колонка Directory_ связывает компонент с виртуальной директорией, которая будет ассоциирована с местом установки конкретного компонента. Думаю, на этом кратенький рассказ о внутреннем устройстве базы данных пакета .msi можно закончить. Конечно, кроме файлов, компонентов и папок, база данных инсталлятора включает описание опций (feature), действий (action), UI и пр. При наличии интереса у читателей, готов подготовить более подробный обзор структуры .msi пакетов.  Для того, чтобы упростить разработку инсталляционных пакетов и облегчить дальнейшую поддержку были разработаны различные инструменты, позволяющие визуализировать и автоматизировать некоторые рутинные процессы. К таким продуктам, например, относятся InstallShield, InnoSetup (не позволяет создавать .msi-файлы - только .exe)  и AdvancedInstaller. Несмотря на удобство использования, данные продукты являются платными (стоимость некоторых продуктов довольно кусачая за исключением Inno Setup - он бесплатный), не имеют открытых репозиториев или создают сложности при корпоративной разработке при мерже проектов. Поэтому наиболее очевидным выбором для большинства интерпрайз-проектов является использование The Windows Installer XML (WiX) toolset.  Для начала работы с этим инструментом вам необходимо произвести его установку. Перейдите по ссылке и скачайте и установите WiX Toolset build tools и WiX Toolset Visual Studio Extension (в соответствии с используемой вами версией Visual Studio). Также, есть расширение для Rider – его можно найти в маркете IDE. После установки компонентов Wix предлагаю перейти в Visual Studio и приступить к созданию проекта Blazor для которого мы будем писать инсталлятор. File -> New Project -> Blazor WebAssambly App. Назовем проект BlazorAppForInstaller. На следующем шаге обязательно поставьте галочку ASP.NET Core hosted и выберите .Net 6.0. После того как проект был создан опубликуем его:  Щелкнуть правой кнопкой мыши по BlazorAppForInstaller.Server -> Publish -> Target: Folder -> Finish.  Проект должен быть опубликован в указанную вами папку (в моем случае, это ~\BlazorAppForInstaller\Server\bin\Release\net6.0\publish\): содержимое этой директории мы и будем упаковывать в инсталлятор.  Щелкнуть правой кнопкой мыши по названию решения Add -> New project В  поиске вбиваем Wix В результатах поиска выбираем Setup Project for Wix v3 и нажимаем Next В поле Project name вводим названием. В моем случае это InstallerForBlazorApp. Щелкаем по кнопке Create.   Для сборки инсталлятора необходимо описать компоненты (то есть файлы установки).  Если бы мы имели дело с простым проектом, состоящим из десятка файла, то вполне допустимо их описать вручную. Однако, когда речь заходит о суровой интерпрайз разработке или просто о больших проектах, включающих кучу файлов (особенно это касается веба) довольно сложно (а зачастую невозможно) становится следить за добавлением или удалением файлов. Именно поэтому, мы воспользуемся консольной утилитой Harvest Tool (heat.exe), который можно найти в директории ~/WiX Toolset v3.11\bin (по умолчанию, должна быть создана при установке в C:/Program Files (x86)). Подробно ознакомиться с возможностями Heat.exe можно в официальной документации https://wixtoolset.org/documentation/manual/v3/overview/heat.html  Я предлагаю сделать запуск heat.exe частью сборки проекта, чтобы сэкономить силы в процессе разработки – благо проекты Wix позволяют интегрировать генерацию описания непосредственно в файл проекта .wixproj. Щелкаем правой кнопкой мыши по проекту инсталлятора -> Add Reference — открываем папку, в которую был установлен WiX, подкаталог bin, файл WixUtilExtension.dll С использованием любого текстового редактора откройте файл InstallerForBlazorApp.wixproj и вставьте следующий кусок кода перед закрывающимся тегом </Project> Как вы видите, код помещен внутрь тега <Target Name=”BeforeBuild”>  описывающий действия, которые должны быть выполнены непосредственно перед сборкой инсталлятора. К таким действиям относится объявление констант препроцессора, к которым можно будет обращаться до этапа сборки проекта. Так, переменная (да, это константа, но нейминг Wix периодически противоречит самому себе) BasePath используется для обозначения пути проекта, который будет упакован в инсталлятор. Настоятельно рекомендую использовать относительные пути настолько насколько это возможно. Далее, с помощью тега <Heat> и его свойств мы вызываем утилиту Heat.exe и передаем ей необходимые аргументы: OutputFile="ComponentsGenerated.wxs" – путь до файла, в котором хранятся компоненты для установки DirectoryRefId="INSTALLFOLDER" – ссылка на папку в которую будет произведена установка приложения (по сути это переменная в которую будет записан путь до директории установки) ComponentGroupName="PublishedComponents" – наименование группы компонентов, которые были сгенерированы. Используется как алиас для множества компонентов и является абстракцией, существующей исключительно в рамках проект Wix. Когда инсталлятор будет скомпилирован, то в таблицах .msi файла вы не найдете сущности соответствующей группе компонентов AutoGenerateGuids="false" и GenerateGuidsNow="true" – настройка генерирации GUID для компонентов в момент вызова утилиты Heat. Такая конфигурация заставляет Heat создать для каждого компонента уникальный GUID в момент запуска таски, т.е. на этапе сборки проекта. Такой подход минимизирует количество ошибок, возникающих при компиляции проекта Wix. ToolPath="$(WixToolPath)" – путь до директории с установленным Wix Toolset. Рекомендую использовать переменную окружения WixToolPath, которая создается при установке Wix. По большому счету, прописывать вручную путь придется только в том случае, если вы планируете использовать Wix toolset без установки через стандартный инсталлятор. PreprocessorVariable="var.BasePath" – переменная препроцессора, которая используется в качестве части пути (указывает на директорию) до файла-компонента. Мы ее объявили внутри тега <DefineConstants>  RunAsSeparateProcess="$(RunWixToolsOutOfProc)"– обязательно указывайте этот атрибут, если работаете в Visual Studio 2022 и собираете 64-битный проект. В противном случае Visual Studio будет закрываться при каждой попытке построить проект. Эту проблему обсуждали здесь SuppressRootDirectory="true" – необходим для избежания дублирования родительских папок компонента.  SuppressCom="true", SuppressFragments="true" и SuppressRegistry="true" – несмотря на то, что очень не велика вероятность, что в вашем проекте окажутся COM объекты, файлы реестров и фрагменты установщика лежащие в одной папке с основным проектом, рекомендуется дополнительно запретить включать такие файлы в процесс создания описания компонентов из-за высокой вероятности возникновения ошибок. Если же, у вас есть необходимость включения таких файлов в проект инсталлятора, рекомендуется добавить отдельное описание таких компонентов.  После того, как вы внесете указанные изменения в файл .wixproj сохраните изменения и постройте проект. В результате, в корневой папке проекта инсталлятора должен появиться файл ComponentsGenerated.wxs.   Добавьте этот новый файл в проект инсталлятора (Solution Explorer -> Add -> Existing Item). Теперь, перейдем в файл Product.wxs. Если кратко, то это основной файл, в котором описывается вся логика создания инсталлятора, его конфигурация и метаданные. Для небольших решений проект wix может в полне себе состоять только из этого файла.  Давайте добавим компоненты в сам инсталлятор. Для этого нам необходимо перед закрывающимся тегом </Wix> удалить узел <Fragment> (он генерится Wix по-умолчанию при создании проекта) на следующую секцию: Таким образом, мы создали группу компонентов ProductComponents, которые будут развернуты по пути соответствующему свойству INSTALLFOLDER (она же виртуальная директория), при этом сами компоненты мы передали по ссылке из группы PublishedComponents, сгенерированной c помощью Heat.exe. Удалите тег <MediaTemplate/> (ищите внутри тега Product) - он используется в качестве заглушки и, если его не удалить, то вы обнаружите, что вместе с .msi-файлом генерится .cab-файл. Нам же необходимо поместить его внутрь файла .msi. Для этого перед закрывающимся тегом <Product> необходимо добавить тег <Media>: Тем самым мы описываем компонент media.cab – архив с файлами для установки. EmbedCab=”yes” – указывает на то, что архив будет встроен в .msi-файл, а свойство DiskPrompt="CD-ROM #1" – является легаси-наследием со времен, когда программные продукты распространялись на материальных носителях и разработчикам инсталляционных пакетов приходилось решать проблему поставки продукта на нескольких дисках из-за нехватки дискового пространства на носителях. Также не забываем добавить Property со ссылкой на DiskPromt, чтобы Windows Installer мог создать сообщение пользователю о необходимости вставить определенный диск. Обращаю внимание, что DiskPrompt является необязательным, и, если вы не планируете поставлять ваш продукт на физическом носителе, то и проставлять его ненужно (то же самое касается и Property Id="DiskPromt"). Теперь нам необходимо добавить интерфейс для взаимодействия с пользователем: сделать это можно двумя путями. Или самостоятельно описывать интерфейс, или воспользоваться готовыми шаблонами, которые поставляются вместе с wix. Так как разработка интерфейса – это очень объемная тема, мы воспользуемся готовым решением. Щелкаем правой клавишей по названию проекта в Solution Explorer -> Add Reference — открываем папку, в которую был установлен WiX, подкаталог bin, файл WixUIExtension.dll. Снова перед закрывающимся тегом <Product> добавляем следующий код: Wix предлагает пользователю несколько сценариев пользовательского интерфейса один из которых называется WixUI_InstallDir и, как можно догадаться из названия, позволяет пользователю выбирать в какую директорию будет произведена установка продукта. Путь до выбранной директории передается через свойство INSTALLFOLDER, которое используется в шаблоне WixUI_InstallDir на что и указывается Id="WIXUI_INSTALLDIR".  Для установки проекта, в нашем случае, описанных шагов недостаточно – не случайно же был выбран web-проект: теперь его нужно затащить на IIS. Разработчики Wix позаботились о нас и добавили в пакет поставки WixIISExtention – расширение, позволяющее развернуть приложение на web-сервере. Подключаем расширение WixIISExtention: правая кнопка маши по проекту -> Add Reference — директория в которую установлен WiX, подкаталог bin, файл WixIISExtension.dll. У меня здесь C:\Program Files (x86)\WiX Toolset v3.11\bin\WixIISExtension.dll Снова правая кнопка мыши по проекту инсталлятора -> New item… -> Wix/v3 и выбираем Installer File (.wxs). Назовем его IISConfiguration.wxs. Стираем все, что находится внутри файла и заменяем на следующее содержание: Давайте разберем по тегам содержимое файла: внутри тега Wix добавляем ссылку на IISExtention c помощью вот этого атрибута xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension". Не забываем это делать, иначе проект не заведется. внутри тега Fragment начинаем описывать настройку IIS. Так как все компоненты должны получить путь установки обязательно упаковываем их в тег Directory или DirectoryRef, если директория уже объявлена. В нашем случае это TARGETDIR – корень виртуального каталога, объявленный на стадии создании проекта Wix    объявляем компонент с помощью тега Component, присваиваем ему ID – не забывайте, что Id должен быть уникальным для каждого компонента (помним, что для базы данных Windows Installer он выполняет роль первичного ключа). Генерим GUID любым удобным для вас способом. Я предпочитаю использовать средства Visual Studio (Tools -> Create GUID и выбрать Registry Format). Указываем компоненту свойство keypath=”yes” – это необходимо, чтобы установщик удостоверился, что данный компонент не был ранее установлен чтобы избежать перетирания существующих версий компонента (будет заменен только в том случае, если будет предложена более новая версия компонента), а также для осуществления восстановления. Внутри компонента описываем настройки AppPool для IIS: прописываем уникальный Id, имя пула, указываем тип учетной записи – в нашем случае это LocalSystem (желающие получить дополнительную информацию о них могу перейти по ссылке https://windowsnotes.ru/iis/application-pool-identities-v-iis/). Указываем режим конвейера Integrated и последнюю доступную версию .net clr. После того, как нами были описаны настройки WebPool’а мы должны сконфигурировать сайт, для чего снова создается новый компонент. Назовем его «InstallWebsite»:    Конфигурация сайта производится с использованием свойств тега <iis:WebSite>. Через свойства тега передаем его Id (помним, что он должен быть уникальным), описание и  виртуальную директорию. Чтобы после установки получить сразу работающий проект, не требующий от пользователя дополнительных телодвижений добавим свойствам AutoStart и StartOnInstall значение yes. С помощью свойств тега iis:WebAddress передадим адрес сайта: в нашем случае он запустится локально на порту 8085 (можете выбрать любой доступный порт) В теге iis:WebApplication мы указали связь сайта с WebPool’ом    На этом IIS можно считать сконфигурированным. Теперь создаем группу компонентов с названием BlazorAppIisConfiguration (см. 35-ю строчку) и закидываем в нее два наших созданных выше компонента (с описанием конфигурации AppPool’а и сайта). Снова открываем файл Product.wxs. Добавляем перед закрывающимся тегом </Product> тег <Feature> со следующим содержимым:    Первые два свойства традиционно Id и Название. Что касается свойства Level – оно служит для включения (при значении «1») или исключения (при значении «0») фичи из процесса установки. Делается это, как правило, путем добавления публичного свойства (тег Property), которое может принимать значение через аргументы командной строки или иными способами и последующим использованием через тег Condition, который должен располагаться внутри тега <Feature>. Обязательно разберем работу с условиями, но не в этой статье.  Свойство ConfigurableDirectory указывает на название виртуальной директории в которой будет лежать наш программный продукт, нуждающийся в разворачивании на IIS. Свойства AllowAdvertise="no" и Absent='disallow' используем с указанными значениями, так как мы не планируем предоставлять пользователю возможность кастомизации процесса установки в части управления параметрами настройки IIS и записи файлов проекта. Далее, внутрь указанного тега кладем ссылки на описанные ранее группы объектов (см. 7 и 8 строчки). Теперь нам необходимо установить связь виртуальной директории, являющейся корневой для нашего проекта, с адресом в дисковом пространстве компьютера пользователя куда будет непосредственно установлен проект. Делается это следующим образом: При создании проекта Wix внутри файла Product.wxs сразу после тега <Product> у вас уже будет шаблон, обозначенный тегами <Fragment>  - замените его на код указанный выше. По сути, несмотря на то, что теги являются вложенными и кажется, что при установке будет создана иерархическая структура, фактически все файлы будут установлены в папку, которая будет выбрана пользователем во время установки, т.к. property WIXUI_INSTALDIR (как было описано выше, с помощью этого свойства шаблону UI передается виртуальный путь) содержит ссылку на виртуальную директорию INSTALLFODER, которая является корнем для нашего проекта. Что касается тега Fragment, то с помощью него достигается структурирование проекта. Раздел, помещенный внутрь тега Fragment, может быть перемещен в отдельный файл или расположен практически в любом месте файла. Иначе, пришлось бы все инструкции для создания инсталлятора помещать внутрь тега Product. Внимательный читатель заметит, что в конечном варианте отсутствует следующий кусок, который был описан вначале: Для лаконичности .xml-описания в нашем случае можно отказаться от создания еще одной группы компонентов внутри файла Product.wxs и добавить ссылку на существующую группу в Feature- см. 26 строку файла Product.wxs. После того, как все необходимые .xml документы будут созданы, давайте соберем проект: правая кнопка мыши по проекту wix -> build. После сборки из папки проекта перейдем по следующему пути: ~\InstallerForBlazorApp\bin\Release (или Debug - в зависимости от выбранной вами конфигурации сборки проекта в Visual Studio) – в нем лежит собранный инсталлятор InstallerForBlazorApp.msi. Запускаем его: Принимаем лицензию (так как мы не передавали файл лицензии, то имеем Lorem impsum…) Далее мы имеем возможность выбрать директорию, в которую будет произведена установка продукта (по умолчанию, Program Files (x86)) Next, next, install. После положительного ответа на запрос разрешения на установку, продукт будет развернут в указанной директории. Убедиться в этом вы можете сами, перейдя в директорию установки. Однако, это еще не говорит о том, что все прошло по плану. Откройте IIS -> Application Pool. Там должен появится созданный нами новый пул приложений А в папке сайтов появится новый BlazorAppWasm Нажимаем на Browse: http://127.0.0.1:8085/ и в браузере открывается страница установленного проекта, после чего установку можно считать успешной. Удалить приложение вы можете или через установку и удаление программ, или непосредственно через инсталлятор путем его повторного запуска. При этом, будет произведено не только удаление файлов, но и пула с сайтом из IIS.  Таким образом, можно резюмировать, что технология Wix позволяет относительно быстро описать и собрать инсталлятор (который позволяет произвести выбор директории установки) для достаточно большого проекта – главное знать какие теги и в каком порядке использовать.  При наличии у хабрчан интереса к данной теме, я планирую подробно рассказать про работу с реестрами, создание пользовательских действий, разработку интерфейса, внедрение скриптов в процесс установки, написание бутстрапера, а также использование расширений и инструментов, поставляемых вместе с Wix.  Разработчик .Net