Pull to refresh

Автоматическое добавление файлов в WiX инсталлятор

Website development *
Sandbox
    Добрый день, коллеги. В своей статье я хочу осветить проблему, с который может столкнуться разработчик, желающий написать свой инсталлятор с помощью технологии WiX.
Думаю, что многие программисты сталкивались с задачей написания инсталлятора для своего программного продукта. Вот и меня постигла та же участь. Решено было использовать WiX. Необходимо было сделать автоматическую сборку инсталлятора на build server. Проекты, из которых берутся файлы для инсталляции, могут меняться, и поэтому нельзя жестко указать список файлов, которые необходимо добавить в инсталлятор.



    Проект у нас пишется под MS Visual Studio 2010. Тем, кто не пишет свой проект на ней, а пользуется только для написания инсталлятора, эта статья тоже будет интересна, так как в данном случае не важно под какой средой разработки вы работаете.
С WiX’ом в комплекте идет утилита heat.exe, которая служит инструментом для автоматического сбора файлов из папок или проектов (есть еще несколько возможных вариантов). Консольное приложение, с командами которого Вы можете ознакомиться тут.
    При начальном изучении вопроса вариант с автоматическим сбором файлов из output-папок проектов казался самым хорошим и удобным вариантом. Для этого всего лишь нужно добавить ссылку на проект, из которого необходимо забрать output и произвести небольшие изменения с файлом проекта (преимущество для тех, кто разрабатывает по VS). Но в процессе изучения этой возможности выяснилось, что утилита heat.exe не собирает из output ничего кроме *.exe файла проекта и конфигурационных файлов. Долгие поиски причин этой проблемы привели к тому, что, как оказалось, такая ошибка уже известна с марта 2010 года, но она до сих пор не устранена.
    Так самый удобный вариант отпал, а задача осталась прежней — добавлять файлы надо автоматически, начался процесс изучения иных путей решения. Забегая вперед, могу сказать, что, конечно же, решение было найдено!
    У утилиты heat.exe есть возможность добавлять файлы не только из проектов, как было сказано выше, а так же возможность собирать их из папки, которую ей можно указать. Этот вариант я и начал исследовать. Выяснилось, что heat.exe добавляет уже все файлы из папки, а не как из проекта, только выборочные. Это уже была маленькая победа. Но нет худа без добра.
    При запуске утилиты heat.exe можно указать, какие типы файлов нужно собирать из папки. Меня интересовали 2 категории – «Binaries» и «Content». Первая категория отвечает за файлы *.dll и *.exe, а вторая – за конфигурационные файлы. На практике же оказалось, что утилите все равно, передаю я эти параметры или нет. Она, как ни в чем не бывало, продолжала собирать все, что есть в папке. Такой вариант никак не мог меня устроить.
    Немного отвлечемся от самой утилиты и поговорим о том, что же в итоге я хотел получить. Мне необходимо получить файл, в котором будет описание файлов в нужном формате. Файл представляет собой xml-документ, в котором хранится идентификатор файлов и пути к ним. Идентификатором является GUID. Не использовать автоматизированное средство и набивать руками такой файл — не самая приятная задача.
    Утилита heat.exe как раз и генерирует такой файл. Но в моем случае, в него попадает всякий мусор. Однако, так как это xml-документ, я могу применить к нему XSLT-преобразование. Достаточно удалить из получившегося файла описание ненужных файлов. Да и к моей радости, heat.exe может принимать, как параметр, такой файл.
    В результате мною был написан файл с преобразованиями и передан на вход heat.exe. И вот я уже счастливый обладатель нужного мне файла. Осталось только записать правильную строку запуска heat.exe в Pre-Build Event проекта инсталлятора и добавить сгенерированный файл в проект. Плоды долгого труда увенчались успехом и нужная цель была достигнута. Инсталлятор собирается автоматически без вмешательства живого организма.
    Все таки мы читаем эту статью не ради красного словца автора, а хотим понять, как же весь этот набор букв превратить в работающий проект.

    Создадим новый проект инсталлятора и назовем его «AutoAddingFilesInstaller». Добавим в наш проект новый файл, который назовем «Output» Рис. 1.

image
Рисунок 1 Содержание файла проекта

    В этом файле мы добавим описание групп файлов. В своем проекте я разбил файлы на 2 группы, такие же которые использует heat.exe:
  • Binaries – все *.dll и *.exe файлы.
  • Content – конфигурационные файлы.


Как же будет выглядеть наш файл? Посмотрим на него.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.  <Fragment>
  4.   <DirectoryRef Id="INSTALLLOCATION">
  5.    <Directory Id="BinariesDir" ComponentGuidGenerationSeed="DBA8384E-CE1B-41af-B573-4203FB8A6A3B">
  6.     <Directory Id="Output.Binaries" />
  7.    </Directory>
  8.   </DirectoryRef>
  9.  
  10.   <DirectoryRef Id="INSTALLLOCATION">
  11.    <Directory Id="ContentDir" ComponentGuidGenerationSeed="35A3DCB8-4F85-4e3f-AC83-C51B78C04B94" Name="NewConfigurationFiles">
  12.     <Directory Id="Output.Content" />
  13.    </Directory>
  14.   </DirectoryRef>
  15.  
  16.   <ComponentGroup Id="OutputGroup">
  17.    <ComponentGroupRef Id="Output.Binaries"/>
  18.    <ComponentGroupRef Id="Output.Content"/>
  19.   </ComponentGroup>
  20.  
  21.  </Fragment>
  22. </Wix>
* This source code was highlighted with Source Code Highlighter.


    Что же тут есть что? Я создал 2 директории(«BinariesDir», «ContentDir»), которые находятся в папке, куда устанавливается программа – «INSTALLLOCATION». Таким образом, я создал 2 ссылки на то, какие директории с файлами буду добавлены в проект.
    Дальше нам надо создать группу компонентов, в которую войдут все выбранные нами типы файлов. Назовем ее «OutputGroup». В ней хранится ссылка на наши созданные директории. Таким образом мы говорим о том, что хотим собирать информацию из них.
    Теперь создадим файл «OutputFiles», в котором и будет храниться список наших файлов. Этот файл будет автоматически заполнять утилита heat.exe. Пока мы сделаем этот файл почти пустым. Добавим туда только ссылки на наши созданные папки. Вот так он должен выглядеть:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.  <Fragment>
  4.   <DirectoryRef Id="Output.Content">
  5.   </DirectoryRef>
  6.   
  7.   <DirectoryRef Id="Output.Binaries">   
  8.   </DirectoryRef>
  9.  </Fragment>
  10.  
  11.  <Fragment>
  12.   <ComponentGroup Id="Output.Content">   
  13.   </ComponentGroup>
  14.   <ComponentGroup Id="Output.Binaries">   
  15.   </ComponentGroup>
  16.  </Fragment> 
  17. </Wix>
* This source code was highlighted with Source Code Highlighter.


    Теперь нам надо добавить в «Фичу» описание нашей группы «OutputGroup», что бы инсталлятор знал, что нужно ее добавить в себя. Для этого переходим в файл «Main» и находим в нем тег «Feature». В него мы помещаем запись о нашей группе. В результате это выглядит так:

  1. <Feature Id="ProductFeature" Title="AutoAddingFilesInstaller" Level="1">
  2.    <ComponentGroupRef Id="OutputGroup" />
  3.    <ComponentGroupRef Id="Product.Generated" />
  4. </Feature>
* This source code was highlighted with Source Code Highlighter.


    Убедившись, что все написано правильно (возможно, даже copy-paste’но), пробуем собрать наш проект. Все должно пройти без ошибок.
    Вся инфраструктура у нас успешно создана. Теперь перейдем непосредственно к утилите heat.exe. Зайдем в свойства нашего проекта и перейдем на вкладку «Build Events». На этой вкладке будет окно «Pre-build Event Command Line». Так как нам надо, чтобы информация о наших файлах попадала до сборки проекта, в это окно мы и будем добавлять вызов утилиты.
    Начнем формировать строку вызова. Сначала добавим "%WIX%\bin\heat.exe". Тем самым у нас будет путь к утилите. Теперь добавим параметр «dir». Это будет означать то, что мы хотим собрать информацию из папки.
Небольшая формальность. Зайдем в папку нашего проекта и создадим там новую папку «source». В нее положим файлы, которые хотим обработать. Вы можете указывать папку, в которой у вас будет output вашего проекта или папку, куда будут собираться необходимые файлы из нескольких проектов. Это мы сделаем сейчас только ради теста, что бы убедится в работоспособности нашего проекта. Вот так выглядит наша тестовая папка изнутри Рис 2.

image
Рисунок 2 Содержание папки, из которой будут браться файлы

    И так, сейчас наша строка выглядит так: "%WIX%\bin\heat.exe" dir $(ProjectDir)source". Теперь добавим в эту строку тот файл, в который будет сгенерирована информация о файлах. Такой файл у нас уже создан – это «OutputFiles».
    В итоге мы видим, что наша строка приобрела такой вид: "%WIX%\bin\heat.exe" dir "$(ProjectDir)source" -o "$(ProjectDir) OutputFiles.wxs" -gg –sfrag

    При изучение возможностей heat.exe мы уже выяснили, что она не может фильтровать типы файлов, которые находятся в папке. Но мы можем использовать xslt-преобразование. Был написан файл «Wix.xslt», который делает преобразование файла сгенерированного heat.exe. Этот файл выглядит вот так:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  3.   xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
  4.   xmlns="http://schemas.microsoft.com/wix/2006/wi"
  5.   exclude-result-prefixes="wix"
  6. >
  7.   <xsl:output method="xml" indent="yes"/>
  8.  
  9.   <xsl:template match="wix:Wix">
  10.  
  11.     <!--Выбираем *.config за исключением vshost и App.config-->
  12.     <xsl:variable name="content" select="wix:Fragment/wix:DirectoryRef/wix:Directory/wix:Component[substring(wix:File/@Source, string-length(wix:File/@Source) - 5) = 'config' and not(contains(wix:File/@Source, 'vshost')) and not(substring(wix:File/@Source, string-length(wix:File/@Source) - 9) = 'App.config')]"/>
  13.  
  14.     <!--Выбираем *.exe & *.dll за исключением vshost-->
  15.     <xsl:variable name="binaries" select="wix:Fragment/wix:DirectoryRef/wix:Directory/wix:Component[substring(wix:File/@Source, string-length(wix:File/@Source) - 2) = 'dll' or substring(wix:File/@Source, string-length(wix:File/@Source) - 2) = 'exe' and not(contains(wix:File/@Source, 'vshost'))]"/>
  16.  
  17.     <Wix>
  18.       <Fragment>
  19.         <DirectoryRef Id="Output.Content">
  20.           <xsl:apply-templates select="$content"/>
  21.         </DirectoryRef>
  22.  
  23.         <DirectoryRef Id="Output.Binaries">
  24.           <xsl:apply-templates select="$binaries"/>
  25.         </DirectoryRef>
  26.       </Fragment>
  27.  
  28.       <Fragment>
  29.         <ComponentGroup Id="Output.Content">
  30.           <xsl:for-each select="$content">
  31.             <ComponentRef Id="{@Id}"/>
  32.           </xsl:for-each>
  33.         </ComponentGroup>
  34.         <ComponentGroup Id="Output.Binaries">
  35.           <xsl:for-each select="$binaries">
  36.             <ComponentRef Id="{@Id}"/>
  37.           </xsl:for-each>
  38.         </ComponentGroup>
  39.  
  40.       </Fragment>
  41.  
  42.     </Wix>
  43.  
  44.   </xsl:template>
  45.  
  46.  
  47.   <xsl:template match="wix:Component">
  48.     <Component Id="{@Id}" Guid="{@Guid}">
  49.       <File Id="{wix:File/@Id}" Source="{concat('..\..\source', substring(wix:File/@Source, 10))}" />
  50.     </Component>
  51.   </xsl:template>
  52.  
  53. </xsl:stylesheet>
* This source code was highlighted with Source Code Highlighter.


    В данном файле прописаны название групп, а так же путь к папке «source». Если Вы хотите использовать в своих целях, то нужно поменять только эти поля.
В результате всех этих действий в окне «Pre-build Event Command Line» у нас должна быть запись:
"%WIX%\bin\heat.exe" dir "$(ProjectDir)source" -t "$(ProjectDir)Wix.xslt" -o "$(ProjectDir)OutputFiles.wxs" -gg –sfrag.

    После этого мы можем собрать наш проект и увидим, что файл «OutputFiles» изменился.
Теперь он выглядит вот так:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.   <Fragment>
  4.     <DirectoryRef Id="Output.Content">
  5.       <Component Id="cmpD86120B7C4146F9A886C539A986173D0" Guid="{1B979A9E-BB11-48FD-B5B6-96FB50A94837}">
  6.         <File Id="fil1A7C67BC02C177948357049406DE080B" Source="..\..\source\ExampleConfigurationFile.config" />
  7.       </Component>
  8.     </DirectoryRef>
  9.     <DirectoryRef Id="Output.Binaries">
  10.       <Component Id="cmp8DD829F42DCE2122C6CC695CE5584A8E" Guid="{7578DAA3-70C9-4565-A755-91EC145DF679}">
  11.         <File Id="filE6BF179915D90D217649A4511F1C7745" Source="..\..\source\ExampleDll.dll" />
  12.       </Component>
  13.       <Component Id="cmp6594B36D2CD42159E7EB3F6B4AAE8285" Guid="{AB0630AA-7EB5-4A8C-8DD3-A4C23742D954}">
  14.         <File Id="fil008A960D6DBFFE7FBE45D2B2AF6AFA3B" Source="..\..\source\ExampleExe.exe" />
  15.       </Component>
  16.     </DirectoryRef>
  17.   </Fragment>
  18.   <Fragment>
  19.     <ComponentGroup Id="Output.Content">
  20.       <ComponentRef Id="cmpD86120B7C4146F9A886C539A986173D0" />
  21.     </ComponentGroup>
  22.     <ComponentGroup Id="Output.Binaries">
  23.       <ComponentRef Id="cmp8DD829F42DCE2122C6CC695CE5584A8E" />
  24.       <ComponentRef Id="cmp6594B36D2CD42159E7EB3F6B4AAE8285" />
  25.     </ComponentGroup>
  26.   </Fragment>
  27. </Wix>
* This source code was highlighted with Source Code Highlighter.


    Если мы откроем полученный инсталляционный файл «AutoAddingFilesInstaller.msi» с помощью Orca, то увидим, что в разделе «File» у нас появились записи о наших 3 файлах Рис. 3.

image
Рисунок 3 Содержания файла инсталлятора

    На этом я хочу завершить свою статью. Надеюсь, что она сможет помочь при разработке инсталлятора и сделать его создание как можно более автоматизированным.
Tags:
Hubs:
Total votes 5: ↑5 and ↓0 +5
Views 13K
Comments Leave a comment