Pokud máme hotovou .NET aplikaci nastane někdy také okamžik, kdy jí potřebujeme nasadit u zákazníka. Jeden ze způsobů jak program dostat na klientský počítač zákazníka může být pomoci instalace. Existuje více způsobů jak připravit takový instalační balíček (např. MSI) naší aplikace, jeden z nejlepších je nástroj Windows Installer XML toolset (WiX).
V samotném Visual Studiu je v základu možné vytvářet instalační projekty buď pomoci Visual Studio Installer, nebo od verze 2010 je také možné doinstalovat InstallShield Limited Edition. Oba tyto nástroje umožňují instalaci nadefinovat pomoci různých grafických návrhářů, ale protože se jedná o nástroje jednoduché, je možný výsledek dost omezený. WiX je na rozdíl od nich založen na vytváření zdrojového kódu ve formě XML (XML source code). Instalace tak nelze jednoduše “naklikat”, ale zase možnosti tohoto nástroje jsou tak mnohonásobně větší a nejsme tedy při vytváření MSI balíčku tolik omezeni. XML zdrojové kódy lze navíc ve WiX libovolně rozdělovat do více souborů a z toho plynou další výhody, jako možnost kódy lehce sdílet v týmovém vývoji, ukládat a verzovat v různých Source Control nástrojů, a nebo části generovat jinými nástroji.
WiX je Open Source projekt od Microsoftu (ano opravdu) k dispozici na sourceforge.net, aktuální stabilní verze 3.5 je dostupná i na CodePlex.com. WiX toolset obsahuje samostatné nástroje (se zajímavě zvolenými názvy), ty nejdůležitější jsou např.: Candle (compiler), Light (linker), Heat, Torch (transform tool) a ještě nástroje na dekompilaci Dark a Melt a další. Tyto nástroje se dají používat přímo nebo přes nějaký build nástroj (jako je NAnt) a nebo přímo z Visual Studia. Tato podpora pro Visual Studio se nazývá Votive a doinstaluje se jako součást WiX Toolset v3.5. Kromě template na nový Installer nebo Merge Module obsahuje i podporu pro tvorbu vlastních akcí instalace v .NET (Managed Custom Actions), které je možné v Setupu využívat.
Ještě se zmíním o jednom omezení, které je dobré si uvědomit, než začneme instalaci ve WiXu vytvářet. WiX totiž vytváří pouze vlastní Windows Intaller MSI balíček, na rozdíl např. od jiných nástrojů jako je InstallShield nebo Wise (ovšem zde myslím plné verze), které součástí instalace vytvoří kromě MSI i vlastní Setup, přes který se instalace spouští. (Pak v takové instalaci většinou ani nelze MSI balíček spustit samostatně.) Protože ale Windows Installer nepodporuje v rámci instalace spouštět jiný MSI balíček, nelze toto provést ani ve WiX instalaci. (Ve WiX jsou podporovány Merge Moduly msm a Wixlib knihovny.)
Tento problém se částečně řeší tím, že před vlastní instalací MSI se spustí exe soubor, který se nazývá Bootstrapper (jméno souboru je většinou setup.exe), tento program před instalací aplikace nainstaluje jiné potřebné MSI instalace tzv. Prerequisites. Toto je velmi dobře použitelné na scénář, kdy potřebujeme nainstalovat .NET aplikaci a pro ní .NET Framework 4.0 (zde navíc musí být Framework nainstalován dřív, protože ho může využívat i vlastní instalace). Bootstrapper lze vytvořit vícero způsoby, od napsání vlastního např. v C++ (je potřeba bez .NETu), nebo pomoci různých nástrojů (např. dotNetInstaller), nebo jednoduchý Bootstrapper lze vytvořit přímo ve Visual Studiu, to si ukážeme v poslední části tohoto článku. Naopak takovýto Bootstrapper neřeší, pokud chceme udělat instalaci, ve které se uživatelem vyberou a zadají různé komponenty a jejich parametry, a tyto součásti pak nainstalovat až následně po instalaci naší aplikace. (Toto např. lze v plné verzi InstallShieldu, kde ve scriptu máme událost OnFirstUIAfter volanou až po skončení instalace MSI, a můžeme tam tedy podle hodnot proměnných provést spouštění jiných instalací).
Protože možnosti WiXu jsou ale i tak velké a nelze je popsat všechny, zaměřím se v tomto článku na výše popsaný častý scénář tj. vytvoření instalace kompletně .NET 4.0 aplikace. V této první části vytvoříme základní instalaci a dále v dalších částech tohoto článku budeme do instalace dodávat tyto další funkčnosti:
- Uživatelské prostředí instalace
Prvotní instalaci, která nemá žádné obrazovky, dodáme pomoci WiX knihovny sadu obrazovek, hlavně budeme požadovat výběr adresáře kam se má instalace nainstalovat.
- Provedeme Lokalizaci Instalace
Prostředí instalace budeme požadovat v češtině a angličtině, tj. předvedeme si jak udělat instalaci multijazykovou.
- Změna, Customizace, přidání obrazovek
Ukážeme si postup jak pozměnit některé obrazovky instalace a jak změnit jejich posloupnost.
- Kontrola spuštění, Merge Modules, Custom akce
Doplníme některé další funkčnosti, jako je kontrola operačního systému, verze požadovaného .NET Frameworku. Dále si ukážeme přidání Merge Modulu a vlastní akce.
- Tvorba Bootstrapper
Pro instalaci .NET Frameworku 4.0 a jiných komponent si prostředky Visual Studia vytvoříme jednoduchý Bootstrapper.
Nyní již začneme vytvářet naší prvotní instalaci. Jako příklad pro instalaci použijeme jednoduchou WPF aplikaci s knihovnou a dokumentací, instalovat tedy budeme tyto soubory: TestApplication.exe, TestApplication.exe.config, TestApplication.Library.dll a Test Application.pdf.
Pozn.: Kompletní příklad instalace bude ke stažení v poslední části článku.
Po založení projektu typu Windows Installer XML/Setup Project je vytvořen soubor Product.wxs. Zatím nám bude stačit tento jeden soubor, později jednotlivé části instalace rozdělíme do více souborů. XML kód pro naší prvotní část instalace bude vypadat takto:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define ProductName = "Testovací aplikace" ?>
<?define ProductVersion = "1.00.0000" ?>
<?define ManufacturerName = "IMP spol. s r. o." ?>
<?define ManufacturerWebUrl ="http://www.imp.cz" ?>
<?define DefaultInstallDir = "Test Application" ?>
<Product Id="9d1087df-cf07-4e86-ada3-ea51be107197" Name="$(var.ProductName)" Language="1029" Codepage="1250"
Version="$(var.ProductVersion)" Manufacturer="$(var.ManufacturerName)" UpgradeCode="37af4021-842f-4fc4-a3f1-f2e7861e3c21">
<Package InstallerVersion="301" Compressed="yes" Platform="x86" InstallPrivileges="elevated" InstallScope="perMachine"
ReadOnly="no" AdminImage="no" SummaryCodepage="1250"
Description="Testovací aplikace pro WiX setup" Manufacturer="$(var.ManufacturerName)" Keywords="Installer"
Comments="Copyright © 2011 IMP spol. s r.o., All rights reserved.
http://www.imp.cz" />
<!--Add / Remove Programs product information-->
<Icon Id="MainIcon" SourceFile="Binary\icon.ico"/>
<Icon Id="UninstallIcon" SourceFile="Binary\UninstallIcon.ico"/>
<Property Id="ARPPRODUCTICON" Value="MainIcon" />
<Property Id='ARPCOMMENTS'>Testovací aplikace pro WiX setup</Property>
<Property Id='ARPCONTACT'>$(var.ManufacturerName)</Property>
<Property Id='ARPURLINFOABOUT'>$(var.ManufacturerWebUrl)</Property>
<Media Id="1" Cabinet="TestApplication.cab" EmbedCab="yes" CompressionLevel="mszip" />
<!--Product components-->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="APPLICATIONROOTDIRECTORY" Name="$(var.DefaultInstallDir)">
<Component Id="TestApplication.exe" Guid="79f543da-21a9-4363-908a-a7ba065e860d">
<File Id="TestApplication.exe" Source="Release\TestApplication.exe"/>
</Component>
<Component Id="TestApplication.exe.config" Guid="fe862ead-e74b-4098-af40-1bb4576e8b80">
<File Id="TestApplication.exe.config" Source="Release\TestApplication.exe.config" />
</Component>
<Component Id="TestApplication.Library.dll" Guid="5f3b80c3-dcdc-4dcd-ae11-e8f24b3be4f4">
<File Id="TestApplication.Library.dll" Source="Release\TestApplication.Library.dll" />
</Component>
<Component Id="TestApplication.pdf" Guid="552d9071-f659-4836-844c-223541c22322">
<File Id="TestApplication.pdf" Name="Test Application.pdf" Source="Release\Test Application.pdf" />
</Component>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="$(var.ProductName)">
<Component Id="ApplicationShortcuts" Guid="6e618011-8885-4dd7-9565-ac2281e36186">
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<Shortcut Id="ApplicationStartMenuShortcut" Directory="ApplicationProgramsFolder" Name="Testovací aplikace" Description="Testovací aplikace pro WiX setup"
Target="[APPLICATIONROOTDIRECTORY]TestApplication.exe" WorkingDirectory="APPLICATIONROOTDIRECTORY" IconIndex="0"/>
<Shortcut Id="DocumentationStartMenuShortcut" Directory="ApplicationProgramsFolder" Name="Dokumentace Testovací aplikace" Description="Uživatelská dokumentace Test Application"
Target="[APPLICATIONROOTDIRECTORY]Test Application.pdf" WorkingDirectory="APPLICATIONROOTDIRECTORY" IconIndex="0"/>
<Shortcut Id="ApplicationDesktopShortcut" Directory="DesktopFolder" Name="Testovací aplikace" Description="Testovací aplikace pro WiX setup"
Target="[APPLICATIONROOTDIRECTORY]TestApplication.exe" WorkingDirectory="APPLICATIONROOTDIRECTORY" IconIndex="0"/>
<Shortcut Id="UninstallShortcut" Name="Odinstalovat Testovací aplikaci" Description="Odinstalovat Testovací aplikaci"
Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]"
Icon="UninstallIcon" IconIndex="0"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes" />
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" Name="Desktop" />
</Directory>
<ComponentGroup Id="ApplicationFiles">
<ComponentRef Id="TestApplication.exe" />
<ComponentRef Id="TestApplication.exe.config" />
<ComponentRef Id="TestApplication.Library.dll" />
<ComponentRef Id="TestApplication.pdf" />
</ComponentGroup>
<Feature Id="AlwaysInstall" Title="[ProductName]" Description="Soubory aplikace Test Application" TypicalDefault="install" Display="expand" Absent="disallow" Level="1" >
<ComponentGroupRef Id="ApplicationFiles" />
<ComponentRef Id="ApplicationShortcuts" />
</Feature>
</Product>
</Wix>
Pokud trochu znáte strukturu MSI tak byste zde měly vidět podobnost, místy totiž elementy XML odpovídají tabulkám v MSI databázi a atributy odpovídají sloupcům v těchto tabulkách. Podíváme blíže na některé elementy.
Elementy <?define ?> se používají na deklaraci proměnných (Custom variables), ty lze pak využívat dále v wxs zdrojovém kódu, já zde definuju takové věci jako ProductName, ProductVersion, ManufacturerName atd., které jsou pro každou instalaci jiné, přitom se ale dále používají na více místech. Pro použití takovéto proměnné pak místo přímo hodnoty napíšeme např. $(var.ProductName).
(Dále lze také hodnoty definovat ve vlastnostech projektu a tím za pomoci podmínek upravovat instalaci pro např. rozdílné konfigurace projektu. Pokud bychom proměnné potřebovali využívat ve více souborech, tak je také možné je umístit do samostatného souboru wxi, a ten pak pomoci <?include ?> do jednotlivých wxs připojit.)
<Product Id="9d1087df-cf07-4e86-ada3-ea51be107197" Name="$(var.ProductName)" Language="1029" Codepage="1250"
Version="$(var.ProductVersion)" Manufacturer="$(var.ManufacturerName)" UpgradeCode="37af4021-842f-4fc4-a3f1-f2e7861e3c21">
<Package InstallerVersion="301" Compressed="yes" Platform="x86" InstallPrivileges="elevated" InstallScope="perMachine"
ReadOnly="no" AdminImage="no" SummaryCodepage="1250"
Description="Testovací aplikace pro WiX setup" Manufacturer="$(var.ManufacturerName)" Keywords="Installer"
Comments="Copyright © 2011 IMP spol. s r.o., All rights reserved.
http://www.imp.cz" />
...
</Product>
Element Product slouží k určení který produkt / aplikaci bude installer instalovat a všechny ostatní elementy jsou v něm vnořeny. Kromě GUID ID a jména Name jsou zde ještě důležité atributy Language a Codepage. Language určuje jazyk instalace, uvádí se číslem LCID (Locale ID), např. 1029 je čeština, jejich seznam je zde, Codepage uvádí kódovou stránku celého MSI balíčku kódy jsou sepsány zde nebo zde.
Dále Package element definuje instalační “balík“ produktu, jsou zde uvedeny požadovaná verze Windows Installeru (např. 301 – v. 3.01), Platform="x86" a dále atributy jako Description, Manufacturer, Keywords, Comments, které slouží pro zobrazení informací v Details MSI souboru.
<!--Add / Remove Programs product information-->
<Icon Id="MainIcon" SourceFile="Binary\icon.ico"/>
<Icon Id="UninstallIcon" SourceFile="Binary\UninstallIcon.ico"/>
<Property Id="ARPPRODUCTICON" Value="MainIcon" />
<Property Id='ARPCOMMENTS'>Testovací aplikace pro WiX setup</Property>
<Property Id='ARPCONTACT'>$(var.ManufacturerName)</Property>
<Property Id='ARPURLINFOABOUT'>$(var.ManufacturerWebUrl)</Property>
Dále jsou zde definovány pomoci elementů Icon dvě ikony, které se zahrnou do package instaleru. Jedna je využitá s dalšími informacemi (pomoci elementů Property) pro zobrazení informací o instalovaném produktu v Control Panelu Přídat nebo uprat programy resp. Programy a funkce:
Icony icon.ico a UninstallIcon.ico přidáme do podadresáře Binary, který v projektu vytvoříme, a ze kterého jsou odkazované.
<Media Id="1" Cabinet="TestApplication.cab" EmbedCab="yes" CompressionLevel="mszip" />
Element Media určuje že soubory budou komprimovaný v souboru TestApplication.cab a zahrnutý uvnitř MSI souboru (EmbedCab="yes").
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="APPLICATIONROOTDIRECTORY" Name="$(var.DefaultInstallDir)">
<Component Id="TestApplication.exe" Guid="79f543da-21a9-4363-908a-a7ba065e860d">
<File Id="TestApplication.exe" Source="Release\TestApplication.exe"/>
</Component>
<Component Id="TestApplication.exe.config" Guid="fe862ead-e74b-4098-af40-1bb4576e8b80">
<File Id="TestApplication.exe.config" Source="Release\TestApplication.exe.config" />
</Component>
<Component Id="TestApplication.Library.dll" Guid="5f3b80c3-dcdc-4dcd-ae11-e8f24b3be4f4">
<File Id="TestApplication.Library.dll" Source="Release\TestApplication.Library.dll" />
</Component>
<Component Id="TestApplication.pdf" Guid="552d9071-f659-4836-844c-223541c22322">
<File Id="TestApplication.pdf" Name="Test Application.pdf" Source="Release\Test Application.pdf" />
</Component>
</Directory>
</Directory>
...
</Directory>
Tato nejdůležitější část definuje kam a jaké soubory budou instalovány. Nejprve se zde definuje adresářová struktura, kam bude produkt instalován. Zde je to c:\Program Files\Test Application (resp. c:\Program Files (x86)\Test Application). To je určeno vnořením elementů Directory:
- TARGETDIR
- ProgramFilesFolder - ID ProgramFilesFolder je předefinováno a Windows Installeru známé.
- APPLICATIONROOTDIRECTORY – je nastaveno na “Test Application”, jméno je určeno atributem Name z proměnné $(var.DefaultInstallDir).
V adresáři se nachází elementy Component, které definují části, které budou instalovány. Komponenta může obsahovat jeden nebo více souborů instalace, vytvoření registru, Shortcut, práci s adresářem apod. Zde je pro každý ze čtyř instalovaných souboru vytvořena samostatná komponenta, což je v souladu s doporučením, pokud se bude ale jednat o jiné soubory než exe a dll aplikace, je možné je dát spolu do jedné komponenty. Ještě si popíšeme vlastnosti elementu File, ten musí mít jedinečné ID, dále Name, určující název souboru, ale pokud je stejný jako ID nemusí být uvedeno (zde je pouze u Test Application.pdf, protože v ID nemůže být znak mezera). Element Source určuje odkud je soubor při kompilaci dostupný pro zahrnutí do instalace. Zde jsou všechny soubory aplikace nahrány do podadresáře Release umístěného v adresáři projektu instalace.
Další adresáře, umístěné do elementu directory TARGETDIR, jsou ProgramMenuFolder a DesktopFolder sloužící pro definování vytvoření zástupců aplikace:
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="$(var.ProductName)">
<Component Id="ApplicationShortcuts" Guid="6e618011-8885-4dd7-9565-ac2281e36186">
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<Shortcut Id="ApplicationStartMenuShortcut" Directory="ApplicationProgramsFolder" Name="Testovací aplikace" Description="Testovací aplikace pro WiX setup"
Target="[APPLICATIONROOTDIRECTORY]TestApplication.exe" WorkingDirectory="APPLICATIONROOTDIRECTORY" IconIndex="0"/>
<Shortcut Id="DocumentationStartMenuShortcut" Directory="ApplicationProgramsFolder" Name="Dokumentace Testovací aplikace" Description="Uživatelská dokumentace Test Application"
Target="[APPLICATIONROOTDIRECTORY]Test Application.pdf" WorkingDirectory="APPLICATIONROOTDIRECTORY" IconIndex="0"/>
<Shortcut Id="ApplicationDesktopShortcut" Directory="DesktopFolder" Name="Testovací aplikace" Description="Testovací aplikace pro WiX setup"
Target="[APPLICATIONROOTDIRECTORY]TestApplication.exe" WorkingDirectory="APPLICATIONROOTDIRECTORY" IconIndex="0"/>
<Shortcut Id="UninstallShortcut" Name="Odinstalovat Testovací aplikaci" Description="Odinstalovat Testovací aplikaci"
Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]"
Icon="UninstallIcon" IconIndex="0"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes" />
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" Name="Desktop" />
Všechny zástupci pro aplikaci v program menu i na ploše, dokumentaci aplikace a odinstalování aplikace vytvoříme jednou komponentou. U zástupce Shortcut lze určit ID, adresář, jméno, popis a dále atributy Target, WorkingDirectory případně Arguments určují cestu k souboru a parametry, co bude zástupce spouštět. Icon a IconIndex určují iconu zástupce, pokud je jiná než její Target. U UninstallShortcut obsahuje Icon odkaz na iconu UninstallIcon, kterou jsme již dříve definovali v instalačním balíčku.
Pozn.: Na různých místech lze v wxs souboru v elementech používat zástupy [ProductName], [ProductCode], [Manufacturer] jako např. v určení RegistryValue aplikace. Narazil jsme ale na problém, že tyto zástupy nefungují v atributech elementu Shortcut, nemůžeme tedy použít např.:
<Shortcut Id=… Name="[ProductName]" …
Jednotlivé komponenty instalace můžeme umísťovat do skupin - ComponentGroup. Zde jsme pro všechny komponenty souborů instalace vytvořili skupinu ApplicationFiles.
<ComponentGroup Id="ApplicationFiles">
<ComponentRef Id="TestApplication.exe" />
<ComponentRef Id="TestApplication.exe.config" />
<ComponentRef Id="TestApplication.Library.dll" />
<ComponentRef Id="TestApplication.pdf" />
</ComponentGroup>
Poslední sekcí v našem příkladu je element Feature, definují jednotlivé celky instalace a mapující je na jednotlivé komponenty nebo skupiny komponent.
<Feature Id="AlwaysInstall" Title="[ProductName]" Description="Test Application files" TypicalDefault="install" Display="expand" Absent="disallow" Level="1" >
<ComponentGroupRef Id="ApplicationFiles" />
<ComponentRef Id="ApplicationShortcuts" />
</Feature>
Zde máme pouze jeden celek, do něho definujeme naší skupinu souborů instalace a také vytvoření zástupců aplikace.
Pozn.: Pro takto jednoduchý setup šlo naše 4 soubory definovat celkově v méně komponentách (např. umístit zástupce přímo k souborům, které spouští atd.). Vytvoření takovéto struktury component je ale pak použitelnější při rozšiřování aplikace a instalace. (Také lze v jednom souboru wxs definovat např. pouze adresáře a komponenty a ComponentGroup umístit do souboru jiného, pak by se použil odkaz na adresář elementem DirectoryRef.)
Součástí možného uživatelského rozhraní aplikace může být také obrazovka pro volení těchto feature uživatelem. Proto se může u feature dále definovat, zda má být viditelný, jeho název a popis, případně lze určit, aby se instaloval do jiné cesty než aplikace, nebo aby jí mohl uživatel změnit.
Tím máme v naší první části definovanou instalaci zatím bez obrazovek, tj. při instalaci je rovnou zobrazen standartní průběh MSI balíčku.
V druhé části bude následovat přidání obrazovek uživatelského rozhraní instalace.
Ještě nějaké odkazy na základní dokumentaci a zdroje:
http://wix.sourceforge.net/manual-wix3/main.htm
http://www.tramontana.co.hu/wix
http://ondotnet.com/pub/a/dotnet/2004/04/19/wix.html
http://www.codeproject.com/KB/install/WixWindowsInstallerDemo1.aspx
http://www.codeproject.com/KB/install/WixTutorial.aspx
Generátor GUID identifikátorů: http://www.guidgen.com