In the previous lesson we have learned how to describe the files we want to be installed. Although we saw some simple implementations of automated installer logic to determine whether a given product has already been installed previously, everything went without the customary user interface giving the user a chance to say anything about the installation. So this is the topic we will be treating in this installment.
The Windows Installer doesn't have its built-in user interface (except for a simple progress dialog we've already seen and a few message boxes popping up to inform the user about various errors). Installer packages have to define their own user interface, compile it and carry it around inside their own .msi file. This makes this file somewhat larger (an .msi file with a typical user interface will start just below 300 kB, although this will also depend on the size of the icons and other graphical elements inside) but, in return to this size, will be perfectly customizable to every possible need.
It wouldn't be too much fun to start to develop a complete user interface for ourselves. Fortunately, there is no need to do that. The WiX toolset comes with a standard user interface library, WixUI. This user interface is based on the prefabricated interface in the MSI SDK (bundled with Microsoft's Visual programming environments but also available as a free download). The library provides the complete user interface of a standard installer package, including all standard wizard pages: license agreement, customer information, typical/custom/complete setup types, customization of install target folder, calculation of disk usage requirements, modify/repair/remove and rollback. The only difference is that—for sake of individuality—its dominant color is reddish instead of bluish. However, it only takes to modify a couple of bitmaps and icons if you want to customize that.
2.1 First Steps
We will expand our previous sample with a nice user interface. But before we delve into details, download SampleWixUI. Compile and run it to get a feeling about what it can do. Build it with the following commands (we will discuss the new linker command line argument later):
candle.exe SampleWixUI.wxs
light.exe -ext WixUIExtension SampleWixUI.wixobj
Try the Custom installation and try to change the target folder you install to. When done, start the installation package again, this time it will allow you to modify or remove the program (the same as you can do in Add or Remove Programs clicking on Change).
Let's see how we could achieve all this functionality. The first part remains the same—after all, we want to install the same product, the same files, same components, same features:
<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product Name='Foobar 1.0' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE' UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>
<Package Id='*' Keywords='Installer' Description="Acme's Foobar 1.0 Installer"
Comments='Foobar is a registered trademark of Acme Ltd.' Manufacturer='Acme Ltd.'
InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />
...
<Directory Id="DesktopFolder" Name="Desktop" />
</Directory>
No surprise so far. The structure of the following segment will also look familiar but we have a couple of new attributes:
<Feature Id='Complete' Title='Foobar 1.0' Description='The complete package.'
Display='expand' Level='1' ConfigurableDirectory='INSTALLDIR'>
<Feature Id='MainProgram' Title='Program' Description='The main executable.' Level='1'>
<ComponentRef Id='MainExecutable' />
<ComponentRef Id='HelperLibrary' />
<ComponentRef Id='ProgramMenuDir' />
</Feature>
<Feature Id='Documentation' Title='Description' Description='The instruction manual.' Level='1000'>
<ComponentRef Id='Manual' />
</Feature>
</Feature>
We will have a user interface now, so we need to display something to the user, to inform them about the choice of features they have. Hence the need for some human readable descriptions. Start the compiled installer package again and navigate to the custom setup so that you can see where and how the various UI texts appear.
The contents of the Title attributes are used to populate the treeview on the left of the dialog. The Description texts will appear on the right side inside the rectangle when you click on an item in the tree. The Display attribute (possible values are collapse, expand and hidden) determines whether the specified tree part will be displayed collapsed or expanded initially, or will not be displayed at all.
Level allows us to decide which features will be installed. The usual scenario is to offer three choices to the user: Typical, Complete and Custom. The last two are simple (Complete includes everything and Custom will allow the user to specify everything in finer details) but we have to specify what belongs to Typical. Or, if necessary, we can have more choices. When the installer runs, there will be a prefabricated property called INSTALLLEVEL that can have any value between 1 and 32,767. A feature will be installed if its level is non-zero and not higher than the current value of INSTALLLEVEL.
In our user interface, we will set this INSTALLLEVEL to 3 for a Typical installation and to 1,000 for a Complete one (this second number is rather arbitrary, we could use any other available number). Consequently, we have to mark those features we don't want to include in a Typical with this level. As INSTALLLEVEL will be 3 during such an installation, only those features having a level of 1 to 3 will be installed, anything above that—including our Level=1000 features—will be left alone.
And finally, the most important part: ConfigurableDirectory. By including this attribute and making it refer to INSTALLDIR, thus creating the link to the intended target directory as specified in the innermost Directory tag a few lines earlier, we allow the user to change our originally intended target. If we don't use this attribute, the user can enable and disable the various features the same way but won't be able to modify the installation directory.
2.2 Custom Settings
In this treeview, each entry will have an associated context menu, allowing the user to decide which features should be installed and how. The menu defaults to the following entries:
- This feature will be installed on local hard drive.
- This feature, and all subfeatures, will be installed on local hard drive.
- This feature will be installed to run from network.*
- This feature, and all subfeatures, will be installed to run from network.*
- This feature will be installed when required.
- This feature will not be available.
Using various attributes of Feature, some of these items can be removed or modified, leaving only those that make sense for the feature in question. Basically, there are five possibilities: to install the feature on the local hard drive, to let it run from the installation media, to run it from the network, to install on demand (the feature will be installed when the user tries to activate it for the first time; this is only supported on more recent operating systems) and to not install it at all.
| Attribute and value | Description |
|---|---|
| AllowAdvertise='no' | This feature will be installed when required will be removed from the context menu. The user is not allowed to install this feature on demand. |
| AllowAdvertise='yes' | This feature will be installed when required will appear in the context menu. The user is allowed to install this feature on demand. |
| AllowAdvertise='system' | This feature will be installed when required will only appear in the context menu, allowing the user to install the feature on demand, if the operating system supports this. |
| InstallDefault='local' | This feature, [and all subfeatures,] will be installed on local hard drive will appear in the context menu. |
| InstallDefault='source' | This feature, [and all subfeatures,] will be installed to run from CD will appear in the context menu instead of the original entry ...installed on local hard drive. |
| InstallDefault='followParent' | The actual state (either local or source) will follow the one set in the parent feature. |
| Absent='allow' | This feature will not be available will appear in the context menu. The user can decide whether to install this feature or not. |
| Absent='disallow' | This feature will not be available will be removed from the context menu. The feature is always required, the user is not allowed to turn its installation off. |
* There seems to be a bug in Windows Installer. With features having no files inside, this entry will appear in the context menu even if you use the attributes outlined above to remove it. In this case, add an empty component (with no files inside) to the feature.
2.3 UI Wizardry
And here comes the real magic. WixUI has five different flavors, depending on how sophisticated you want your user interface to be:
- WixUI_Mondo offers the complete interface, welcome page, license agreement, setup type (typical, custom and complete), allows for feature customization, browsing for the target directory and offers disk costing. Maintenance mode dialogs are also included. You should use this flavor when some of your product's features shouldn't be installed by default (in other words, there is a significant and meaningful difference between typical and complete installs).
- WixUI_FeatureTree is similar to the full set but it doesn't allow the user to chose between setup types. It always assumes Custom and goes directly to the feature customization dialog after the user has accepted the license agreement.
- WixUI_InstallDir allows the user to select a destination directory but without presenting the usual customized features page. After having selected the directory, the installation will proceed automatically*.
- WixUI_Minimal features a simplistic user interface with a single dialog combining the welcome and license agreement pages. After that, the installation goes on automatically without allowing the user to customize anything. Use it when your application has no optional features to install.
- WixUI_Advanced is rather similar to WixUI_Minimal as it offers a simple, one-click install but it also allows selecting features and folders if the user chooses to do so.
In order to get a full user interface, all we have to do is to add two lines to include the WixUI interface library into our project:
<UIRef Id="WixUI_Mondo" />
<UIRef Id="WixUI_ErrorProgressText" />
The first reference includes the appropriate user interface library but it doesn't automatically use the localized (or modified, in case of English) error and action texts in the language files. Without the second reference, the installer package will be slightly smaller and will use the stock messages inside Windows Installer.
*Note that if you use this dialog set, you'll have to provide an extra property somewhere in your source:
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
And, finally, we finish it just like in our earlier samples:
<Icon Id="Foobar10.exe" SourceFile="FoobarAppl10.exe" />
</Product>
</Wix>
All the user interface variants come in a common precompiled library. We simply link against this extesion library using the command line switch we have already mentioned. When working inside an integrated development environment, we have to add a reference to this library to achieve the same effect.
candle.exe SampleWixUI.wxs light.exe -ext WixUIExtension SampleWixUI.wixobj
You can customize some visual aspects of the user interface by simply providing replacement files. The default ones reside inside the toolset but you're allowed to create your own replacement bitmaps, icons and license text there. You can also replace selected files, not all of them. Their paths are stored in variables that you can specify either on the command line or directly in the source code:
<WixVariable Id="WixUILicenseRtf" Value="path\License.rtf" /> <WixVariable Id="WixUIBannerBmp" Value="path\banner.bmp" /> <WixVariable Id="WixUIDialogBmp" Value="path\dialog.bmp" /> <WixVariable Id="WixUIExclamationIco" Value="path\exclamation.ico" /> <WixVariable Id="WixUIInfoIco" Value="path\information.ico" /> <WixVariable Id="WixUINewIco" Value="path\new.ico" /> <WixVariable Id="WixUIUpIco" Value="path\up.ico" />
Their meaning and details are:
| ID of file to customize | Description |
|---|---|
| WixUIBannerBmp | 500 by 63 pixels, this bitmap will appear at the top of all but the first page of the installer. |
| WixUIDialogBmp | 500 by 314 pixels, this bitmap will appear on the first page of the installer. |
| WixUIExclamationIco | Exclamation mark icon. |
| WixUIInfoIco | Information sign icon. |
| WixUINewIco | New folder icon. |
| WixUIUpIco | Parent folder icon. |
| WixUILicenseRtf | Preferably, use a simple editor like Wordpad to create it, or if you insist on overly complex applications like Word, consider resaving the final version from Wordpad, anyway. The RTF will be less complicated and smaller. |
2.4 Do You Speak English?
The languages below are currently supported by WiX (to see the status of other languages, visit the localization project page). Make sure you specify both the correct language and codepage codes in both your Product and Package tags; without the proper language settings, you might see error messages to pop up unexpectedly in a language different from the base language of your installer, and without the proper codepage, accented or non-Latin letters might not appear at all.
| Language | Status | Language-Country | Localization file | Language | Codepage |
|---|---|---|---|---|---|
| Chinese, traditional | finished | zh-tw | WixUI_zh-tw.wxl |
1028 | 950 |
| Czech | released | cs-cz | 1029 | 1250 | |
| Danish | finished | da-dk | WixUI_da-dk.wxl |
1030 | 1252 |
| Dutch | released | nl-nl | 1043 | 1252 | |
| English | released | en-us | WixUI_en-us.wxl* |
1033 | 1252 |
| French | released | fr-fr | 1036 | 1252 | |
| German | released | de-de | WixExt_de-de.wxl |
1031 | 1252 |
| Hungarian | released | hu-hu | WixUI_hu-hu.wxl* |
1038 | 1250 |
| Italian | released | it-it | 1040 | 1252 | |
| Japanese | released | ja-jp | 1041 | 932 | |
| Polish | released | pl-pl | 1045 | 1250 | |
| Russian | released | ru-ru | 1049 | 1251 | |
| Spanish | released | es-es | WixExt_es-es.zip |
3082 | 1252 |
| Ukrainian | released | uk-ua | 1058 | 1251 |
* We also offer alternative, cleaner, more fluent, easier-to-understand translations instead of some localization files in the released package. They differ in some places from the usual wording used by Microsoft in their installations but, in our personal opinion, they use simpler and better language. You are completely free to check them out and decide which one you want to use in your install packages.
Released languages are compiled into the WixUI extension library. To use them, simply provide the name of the culture. If you use the integrated environment, there is a Cultures field in the project properties dialog where you can specify the same. If you specify more than one culture identifier in a comma delimited list, a separate installer will be created for all of them.
candle.exe SampleWixUI.wxs light.exe -ext WixUIExtension -cultures:fr-fr SampleWixUI.wixobj
Finished languages (those waiting their approval for release) or alternative language files (either downloaded from the table above or your own) can be specified directly on the command line. In the integrated environment all you need to do is to include the .wxl file among your source files, the appopriate installer will be created automatically.
candle.exe SampleWixUI.wxs light.exe -ext WixUIExtension -loc path/WixUI_hu-hu.wxl -out SampleWixUI.msi SampleWixUI.wixobj
2.5 New Link in the Chain
Although the WixUI interface libraries can handle most usual setup scenarios, modifications or additions are sometimes required. To handle these cases, the WiX source code has to be downloaded, too, because we will need to take a peek at some source files.
In our sample, we will modify the WixUI_Mondo library, adding a new dialog to collect registration information (name, organization, serial number) from the user. The new dialog will appear between the License Agreement and Setup Type dialogs.
To achieve this, we have to provide a new UserRegistrationDlg.wxs file describing this new dialog. You can start with an existing dialog, modifying it, or writing your dialog from scratch. A later lesson describes how to create dialogs and use various interface elements in WiX. Here we only mention a few remarks:
<?xml version='1.0' encoding='windows-1252'?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
We have to author the new dialog as a separate fragment:
<Fragment>
<UI>
<Dialog Id="UserRegistrationDlg" Width="370" Height="270" Title="[ProductName] Setup" NoMinimize="yes">
<Control Id="NameLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="&User Name:" />
<Control Id="NameEdit" Type="Edit" X="45" Y="85" Width="220" Height="18" Property="USERNAME" Text="{80}" />
<Control Id="OrganizationLabel" Type="Text" X="45" Y="110" Width="100" Height="15" TabSkip="no" Text="&Organization:" />
<Control Id="OrganizationEdit" Type="Edit" X="45" Y="122" Width="220" Height="18" Property="COMPANYNAME" Text="{80}" />
<Control Id="CDKeyLabel" Type="Text" X="45" Y="147" Width="50" Height="10" TabSkip="no">
<Text>CD &Key:</Text>
</Control>
<Control Id="CDKeyEdit" Type="MaskedEdit" X="45" Y="159" Width="250" Height="16" Property="PIDKEY" Text="[PIDTemplate]" />
The dialog will be inserted into the original chain of dialogs. We have to specify which dialogs it will step forward or backward to in this chain: License Agreement and Setup Type. We can learn the actual identifiers of these dialogs if we look into the source file of WixUI_Mondo: src\ext\UIExtension\wixlib\WixUI_Mondo.wxs in the download source package. The names are LicenseAgreementDlg and SetupTypeDlg, so this is how we refer to them from this new dialog:
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back">
<Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
</Control>
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
<Publish Event="ValidateProductID" Value="0">1</Publish>
<Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
<Publish Event="NewDialog" Value="SetupTypeDlg">ProductID</Publish>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
<Text>Please enter your customer information</Text>
</Control>
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
<Text>{\WixUI_Font_Title}Customer Information</Text>
</Control>
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
</Dialog>
</UI>
</Fragment>
</Wix>
It is rather easy to incorporate the dialog into the original user interface. Instead of simply referencing WixUI_Mondo as we did earlier, we build our own interface with the UI tag. However, we still want to use the bulk of WixUI_Mondo, so we start by calling it with UIRef and only add our modifications: first, we refer to our new UserRegistrationDlg dialog.
Then we have to specify the remaining two links. The License Agreement dialog used to point to the Setup Type dialog as its successor and vice versa. Now that our own dialog is inserted between them, we have to modify their Next and Back links correspondingly. The easy way is to copy the relevant Publish tags from WixUI_Mondo.wxs and modify the Value attribute to point to our new dialog, without changing anything else:
<UI Id="MyWixUI_Mondo">
<UIRef Id="WixUI_Mondo" />
<UIRef Id="WixUI_ErrorProgressText" />
<DialogRef Id="UserRegistrationDlg" />
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="UserRegistrationDlg" Order="3">LicenseAccepted = "1"</Publish>
<Publish Dialog="SetupTypeDlg" Control="Back" Event="NewDialog" Value="UserRegistrationDlg">1</Publish>
</UI>
The main file will need to define the property we referred to. MaskedEdit controls use various characters to determine what and how will appear in the control, what kind of input the control will accept and how the final data assigned to the receiving property will be formed.
<Property Id="PIDTemplate"><![CDATA[12345<### ###>@@@@@]]></Property>
And this is all there is to it. Now we can build our modified installer with the following commands:
candle.exe SampleWixUIAddDlg.wxs UserRegistrationDlg.wxs light.exe -ext WixUIExtension -out SampleWixUIAddDlg.msi SampleWixUIAddDlg.wixobj UserRegistrationDlg.wixobj
2.6 Think Localized
If we add our own dialogs to WixUI, we might want them to be localized as well. That doesn't require too much extra work. The main source file stays the same, we only have to modify our new dialog in UserRegistrationDlg.wxs. Instead of hardwired texts we will use localizable string references:
<Fragment>
...
<Dialog Id="UserRegistrationDlg" Width="370" Height="270" Title="!(loc.UserRegistrationDlg_Title)" NoMinimize="yes">
<Control Id="NameLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="!(loc.UserRegistrationDlg_UserName)" />
<Control Id="OrganizationLabel" Type="Text" X="45" Y="110" Width="100" Height="15" TabSkip="no" Text="!(loc.UserRegistrationDlg_Organization)" />
...
</Fragment>
</Wix>
Next, create localization files listing these strings in the appropriate culture. Let's call our file UserRegistrationDlg.fr-fr.wxl (the name is up to you, the extension is fixed). In the WixLocalization tag you have to specify the culture and its codepage. Create parallel copies for other languages you want to localize to.
<?xml version="1.0" encoding="utf-8"?> <WixLocalization Culture="fr-fr" Codepage="1252" xmlns="http://schemas.microsoft.com/wix/2006/localization"> <String Id="UserRegistrationDlg_Title" Overridable="yes">???</String> <String Id="UserRegistrationDlg_UserName" Overridable="yes">???</String> <String Id="UserRegistrationDlg_Organization" Overridable="yes">???</String> ... </WixLocalization>
To build the installer from the source, we also need to reference the localization file. In the integrated environment, just include the file in your project and it will be used automatically.
candle.exe SampleWixUIAddDlgLoc.wxs UserRegistrationDlg.wxs light.exe -ext WixUIExtension -cultures:fr-fr -loc UserRegistrationDlg.fr-fr.wxl -out SampleWixUIAddDlgLoc.msi SampleWixUIAddDlgLoc.wixobj UserRegistrationDlg.wixobj
