WiX Toolset Course

WiX Toolset Course

So the WiX Toolset consists of 5 tools used to help software developers move easily between written code and windows installers. It consists of:

The compiler - candle.exe
The linker - light.exe
The Bootstrapper - burn.exe
The Harvester - heat.exe
The Decompiler - dark.exe

msiexec - is the application that reads and installs msi files.

The MSI file mainly consists of a relational database with files.

The installation process is divided into two main parts which are called "server-side" and "client-side". The serverside seems to me to be the part which actually installs and is sometimes called "execute" phase. I wonder if this occurs within the msiexec application itself or whether this a client which reaches to something else. The client-side provides the UI support. Server side has elevated privileges.

Be careful with the XML it's very case-sensitive. Nearly all the names for the tags and attributes begin with a capital letter. It is similar to PascalCase for example the "UpgradeCode" attribute.

Properties

Properties are like variables used during the install process. Public properties are shared between "server-side" and "client-side" parts of the installation process. In order to make a property public just make sure all its letters are capitals, private properties must contain lower case letters.

Properties get added to the property table of the relational database when and only if they are assigned a non-null value.

When logging the properties can be dumped to the log file so don't store secrets.

The following properties are required:

  • Product Code
  • Product Language
  • Product Name
  • Product Manufacturer
  • Product Version

Upgrade Code should also be included so upgrades are possible in future.

There are usually on the Product tag inside the Wix tag

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
 xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Id="*" Name="My App" Language="1033" Version="1.0.1.0"
           Manufacturer="Graeme Inc" UpgradeCode="000121DF-ABCD-4684-C1EF-FEC86B738909">
...
     </Product>
</Wix>

Features and Components

Features are essentially containers of components that should be installed togther.

Features appear in the feature tree in the UI.

There must be at least one feature.

Components are things that should be installed together. Each one is a registry key, a file or shortcut.

Features can have an attribute of Absent set to disallow to prevent a user from being able to disable a feature. For example if you were installing Microsoft Word then being able to install with support for East Asian languages being absent is fine, but you should not be able to install and have the Word Processor absent.

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
 xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Id="*" Name="My App" Language="1033" Version="1.0.1.0"
           Manufacturer="Graeme Inc" UpgradeCode="000121DF-ABCD-4684-C1EF-FEC86B738909">
        <Feature Id="FeatureA" Title="Word Processor" Level="1" Absent="disallow">
            <ComponentRef Id="ModuleA" />
        </Feature>
        <Feature Id="FeatureB" Title="Support for East Asian Languages" Level="1">
            <ComponentRef Id="ModuleB" />
        </Feature>
    </Product>
</Wix>

Sequences

Actions and components are installed in order.

There is a couple of tables such as the InstallUISequence table and InstallExecuteSequence which have an order of things in each sequence. (these sequences ae different for the admin install (network installing) and the adversative installing.

Conditions

Is evaluated to a boolean value to determine if a line in the sequence table runs or not.

Operators: =, <>, <=, >=, < and >.

  • contains >< ("hand" >< "handsome" evaluates to true)
  • starts with <<
  • ends with >>
  • case insensitive equals ~= ("A ~= "a" TRUE, "A" = "a" FALSE (Case sensitive comparison))

"A" < "B" evaluates to true.

Binary Operators AND, OR, XOR (either but not both), EQV (both true or both false), IMP (left is false or right is true)

If a condition has just a property in it then it is true if that property has been given a value, false if undefined.

%Name = environment variable called Name

  • $Mycomponent = action state of specified component. What state it will be in when finished.
  • ?Mycomponent = installer state of specified component. What state it is currently in.
  • &MyFeature = action state of specified feature. What state it will be in when finished.
  • !MyFeature = intaller state of specified feature. What state it is currently in.

The above all resolve to an integer -1 to 4.
-1 - no action
1 - Advertised Feature
2 - Not present Component / Featre
3 - Locally found
4 - In remote location

Formatted Strings (Interpolated strings)

Use [] to put variable in your strings for example:

"A newer version of [ProductName] is already installed."

\[ and \] can be used to escape the characters and prevent this happening.

Directories

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
 xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Id="*" Name="My App" Language="1033" Version="1.0.1.0"
           Manufacturer="Graeme Inc" UpgradeCode="000121DF-ABCD-4684-C1EF-FEC86B738909">
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLFOLDER" Name="My App">
                </Directory>
            </Directory>
        </Directory>
    </Product>
</Wix>

The TARGETDIR Directory tag is part of the MSI file structure so the Wix XML reflects this, but this does not map directly to an actual logical directory on the drive.

The Tag with Id ProgramFilesFolder maps directly to the ProgramFiles Folder for non 64-bit applications. This folder is usually called "Program Files (x86)" on newer versions of Windows. This can alternatively be replaced with the Id ProgramFiles64Folder for the Program Files folder for 64-bit applications. This is normally called "Program Files" on newer versions of Windows.

Embed the cab file

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
 xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Id="*" Name="My App" Language="1033" Version="1.0.1.0"
           Manufacturer="Graeme Inc" UpgradeCode="000121DF-ABCD-4684-C1EF-FEC86B738909">
        <MetaTemplate EmbedCab="yes" />
    </Product>
</Wix>

Msi files store the data in cab files (cabinet files) however to make it easier you can embed the cab in the msi file so you only have one file to install the application. I recommend doing this, so that they cannot become separate as easily. This is done by adding the MetaTemplate with EmbedCab set to "yes" as above.

UI bits

In order to use any of these features you need to include the WiXUIExtention

If you add a UIRef in the Product tag this can be used to add parts of a UI. Adding a UIRef with Id WixUI_FeatureTree will add multiple pages not just the feature selection page. In fact, this is a full UI with a start page, a page that has the license agreement, a customize dialog that has a feature tree which allows the user to select which features they would like installed and displays the file size, a browse button which allows the user to select a folder and checks disk space before attempting to install.

For example

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
 xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Id="*" Name="My App" Language="1033" Version="1.0.1.0"
           Manufacturer="Graeme Inc" UpgradeCode="000121DF-ABCD-4684-C1EF-FEC86B738909">
        <UIRef Id="WixUI_FeatureTree" />
    </Product>
</Wix>

One of the projects I worked on we had to create a unique set of dialogues, which did not already exist and I had to add to it. As far as I can tell the people who worked on it before used Publish tags to select which dialogues were used and to determine what happens when the buttons on them are pressed.

In my case, I created a whole new dialogue rather than using publishing and configuring an existing one, because the others did not meet the needs of the client. My boss kindly pointed me to this list of existing dialogs so that I could easily create one which fits with the existing dialogs. https://github.com/wixtoolset/wix3/tree/c02e48ec301a60eba88a3b519d47e88eeaa4c978/src/ext/UIExtension/wixlib

<Wix ...>
    <Product ...>
        <UI>
	    <UIRef Id="WixUI_CustomizeMyOrgUI" />
	</UI>
    </Product>
    <Fragment>
        <UI Id="WixUI_CustomizeMyOrgUI">
	    <TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
	    <TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
	    <TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
	    <Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
	    <Property Id="WixUI_Mode" Value="InstallDir" />
	    <DialogRef Id="BrowseDlg" />
	    <DialogRef Id="DiskCostDlg" />
	    <DialogRef Id="ErrorDlg" />
	    <DialogRef Id="FatalError" />
	    <DialogRef Id="FilesInUse" />
	    <DialogRef Id="MsiRMFilesInUse" />
	    <DialogRef Id="PrepareDlg" />
	    <DialogRef Id="ProgressDlg" />
	    <DialogRef Id="ResumeDlg" />
	    <DialogRef Id="UserExit" />
	    <Publish
                Dialog="BrowseDlg"
                Control="OK"
                Event="DoAction"
                Value="WixUIValidatePath"
                Order="3">1</Publish>
            <Publish
                Dialog="BrowseDlg"
                Control="OK"
                Event="SpawnDialog"
                Value="InvalidDirDlg"
                Order="4">
                <![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]>
            </Publish>
            <Publish
                Dialog="ExitDialog"
                Control="Finish"
                Event="EndDialog"
                Value="Return"
                Order="999">1</Publish>
            <Publish
                Dialog="WelcomeDlg"
                Control="Next"
                Event="NewDialog"
                Value="SelectAutoUpdate">NOT Installed</Publish>
            <Publish
                Dialog="WelcomeDlg"
                Control="Next"
                Event="NewDialog"
                Value="VerifyReadyDlg">Installed AND PATCH</Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="Back"
                Event="NewDialog"
                Value="SelectAutoUpdate">1</Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="Next"
                Event="SetTargetPath"
                Value="[WIXUI_INSTALLDIR]"
                Order="1">1</Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="Next"
                Event="DoAction"
                Value="WixUIValidatePath"
                Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="Next"
                Event="SpawnDialog"
                Value="InvalidDirDlg"
                Order="3">
		<![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]>
            </Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="Next"
                Event="NewDialog"
                Value="VerifyReadyDlg"
                Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="ChangeFolder"
                Property="_BrowseProperty"
                Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
            <Publish
                Dialog="InstallDirDlg"
                Control="ChangeFolder"
                Event="SpawnDialog"
                Value="BrowseDlg" Order="2">1</Publish>
            <Publish
                Dialog="VerifyReadyDlg"
                Control="Back"
                Event="NewDialog"
                Value="InstallDirDlg"
                Order="1">NOT Installed</Publish>
            <Publish
                Dialog="VerifyReadyDlg"
                Control="Back"
                Event="NewDialog"
                Value="MaintenanceTypeDlg"
                Order="2">Installed AND NOT PATCH</Publish>
            <Publish
                Dialog="VerifyReadyDlg"
                Control="Back"
                Event="NewDialog"
                Value="WelcomeDlg"
                Order="2">Installed AND PATCH</Publish>
            <Publish
                Dialog="MaintenanceWelcomeDlg"
                Control="Next"
                Event="NewDialog"
                Value="MaintenanceTypeDlg">1</Publish>
            <Publish
                Dialog="MaintenanceTypeDlg"
                Control="RepairButton"
                Event="NewDialog"
                Value="VerifyReadyDlg">1</Publish>
            <Publish
                Dialog="MaintenanceTypeDlg"
                Control="RemoveButton"
                Event="NewDialog"
                Value="VerifyReadyDlg">1</Publish>
            <Publish
                Dialog="MaintenanceTypeDlg"
                Control="Back"
                Event="NewDialog"
                Value="MaintenanceWelcomeDlg">1</Publish>
            <Property
                Id="ARPNOMODIFY"
                Value="1" />
            <Dialog
                Id="SelectAutoUpdate"
                Width="370"
                Height="270"
                Title="Auto Update">
                <Control
                    Id="BannerBitmap"
                    Type="Bitmap"
                    X="0"
                    Y="0"
                    Width="370"
                    Height="44"
                    TabSkip="no"
                    Text="!(loc.BrowseDlgBannerBitmap)" />
                <Control
                    Id="BannerLine"
                    Type="Line"
                    X="0"
                    Y="44"
                    Width="370"
                    Height="0" />
                <Control
                    Id="BottomLine"
                    Type="Line"
                    X="0"
                    Y="234"
                    Width="370"
                    Height="0" />
                <Control
                    Id="Description"
                    Type="Text"
                    X="25"
                    Y="23"
                    Width="280"
                    Height="15"
                    Transparent="yes"
                    NoPrefix="yes"
                    Text="You can select whether to turn on automatic updates." />
                <Control
                    Id="Title"
                    Type="Text"
                    X="15"
                    Y="6"
                    Width="200"
                    Height="15"
                    Transparent="yes"
                    NoPrefix="yes"
                    Text="Autoupdates" />
                <Control
                    Id="Autoupdatecheckbox"
                    Type="CheckBox"
                    X="25"
                    Y="50"
                    Width="290"
                    Height="17"
                    Property="AUTOUPDATE"
                    CheckBoxValue="1"
                    Text="Autoupdate" />
                <Control
                    Id="Next"
                    Type="PushButton"
                    X="236"
                    Y="243"
                    Width="56"
                    Height="17"
                    Default="yes"
                    Text="!(loc.WixUINext)">
                    <Publish
                        Event="NewDialog"
                        Value="InstallDirDlg">1</Publish>
                </Control>
                <Control
                    Id="Back"
                    Type="PushButton"
                    X="180"
                    Y="243"
                    Width="56"
                    Height="17"
                    Text="!(loc.WixUIBack)">
                    <Publish
                        Event="NewDialog"
                        Value="WelcomeDlg">1</Publish>
                </Control>
                <Control
                    Id="Cancel"
                    Type="PushButton"
                    X="304"
                    Y="243"
                    Width="56"
                    Height="17"
                    Cancel="yes"
                    Text="!(loc.WixUICancel)">
                    <Publish
                        Event="SpawnDialog"
                        Value="CancelDlg">1</Publish>
                </Control>
            </Dialog>
        </UI>
        <UIRef Id="WixUI_Common" />
    </Fragment>
</Wix>

Searches

These can be used for check pre-requisits. You can run AppSearch to check if there is a application installed and to find their installation directory.

Other options:

  • DirectorySearch - search for existence of folders
  • FileSearch
  • RegistrySearch
  • ComponentSearch - search for windows component
  • IniFileSearch - search inside .ini files
<Property Id="VALUETOBESET">
    <DirectorySearch Id="myDirSearch" Path="C:\foo\bar">
    </DirectorySearch>
</Property>
<Property Id="REQUIREDVALUE" ComplianceCheck="yes">
    <DirectorySearch Id="mySecondDirSearch" Path="C:\David\Bowie\Labrinth">
    </DirectorySearch>
</Property>

If C:\foo\bar folder is found, then the global property called VALUETOBESET is set to the full path name C:\foo\bar otherwise it remains null.

If you want to prevent installation should a folder not be found you can add a ComplianceCheck to the Property so if it does not have have a certain folder it will not go through with the installation. The end result would be this error message which isn't very descriptive

Alternatively you could remove the attribute above and use a launch condition in a condition tag which simply says:

<Condition Message="Before installing this product please make sure the Folder C:\David\Bowie\Labrinth exists first!">
     <![CDATA[Installed OR REQUIREDVALUE]] />
</Condition>

This allows you to display the full message if the REQUIREDVALUE is null AND the product is not already installed. Obviously, if it is already installed and we are uninstalling we don't want this message to appear.

I believe <![CADTA[...]] /> allows for you to type names which might be null without it being invalid XML.

You can also do a FileSearch, but this must be inside a DirectorySearch which should be in property.

<Property Id="VALUETOBESET">
    <DirectorySearch Id="myDirSearch" Path="C:\foo">

        <FileSearch Id="barFileSearch" Path="bar.txt">
        </FileSearch>
    </DirectorySearch>
</Property>

The window registry is as far as I see it an attempt to centralise .INI files which were key value pair files with sections and subsections.

RegistrySearches seek values which are stored in 4 roots:

  1. HKEY_CLASSES_ROOT (HKCR)
  2. HKEY_CURRENT_USER (HKCU)
  3. HKEY_LOCAL_MACHINE (HKLM)
  4. HKEY_USERS (HKU)

The key attribute is the full address which follows the root in making the path to the value for example: "SOFTWARE/Microsoft/Windows NT/CurrentVersion".

Graeme