Mustafa Can Yücel
blog-post-19

WiX 5 and HeatWave

Windows Installer XML (WIX)

WIX is a toolset that builds Windows installation packages from XML source code. The toolset supports a command line environment that developers may integrate into their build processes to build MSI and MSM setup packages. The core of WiX is a set of build tools that build Windows Installer packages using the same build concepts as the rest of your product: source code is compiled and then linked to create executables; in this case .exe setup bundles, .msi installation packages, .msm merge modules, and .msp patches. More officially, WIX is a framework for building installers in ways that are easier to understand by abstracting away the low level function calls while still allowing programmers to write much of the code by hand. In a more advanced scenario, WiX can be used to create a WiX source file from scratch, and this is what wwe will be doing in this blog post.

Don't let the easier part fool you, WiX has a very steep learning curve due to the fact that it is a very low level tool. It is not a drag and drop tool like InstallShield or Advanced Installer. It is a tool that allows you to write XML code that will be compiled into an MSI file. This means that you have to know how the Windows Installer works and how to write XML code. The good news is that WiX is very powerful and flexible. You can do almost anything with it, but you have to know how to do it.

WiX: A Priori

WiX is in the market for quite a long time and it has both matured and changed significantly from its earlier versions. We will be using WiX 5 in this blog post. WiX 5 is the latest version of WiX and it has a lot of new features and improvements.

Installation

The installation of WiX itself is quite straightforward; you can follow the instructions on the official website.

FireGiant has released HeatWave Community Edition to support WiX SDK-style MSBuild projects in Visual Studio. HeatWave supports:

  • Conversion of WiX v3 projects and authoring
  • Building of WiX SDK-style projects
  • Project and item templates
  • Property pages to control how the project builds
HeatWave Community Edition is available free of charge, and can be downloaded from the FireGiant website.

Basic Concepts and Contents of a WXS File

Back in the days of WiX 3 or before, the common way to create WiX projects were having a parent WiX file (*.wsx) that includes all the other WiX files which contain fragments. These fragments were mostly created using heat.exe and burn.exe tools, that were shipped with WiX toolset. However, with the introduction of WiX 4+, the concept of WiX projects has changed. Now, you can create WiX projects in Visual Studio and use the HeatWave extension to create WiX files. Nevertheless, it does not hurt to know how the WiX files are structured and how they work.

XML Declaration and Wix Element

Every WiX file starts with an XML declaration and a Wix element. The Wix element has a xmlns attribute that defines the namespace of the WiX elements. The xmlns attribute is set to http://schemas.microsoft.com/wix/2006/wi for WiX 3 and http://schemas.microsoft.com/wix/2023 for WiX 5. The xmlns attribute is followed by a version attribute that defines the version of the WiX file. The version attribute is set to 3.0 for WiX 3 and 5.0 for WiX 5:

<?xml version=”1.0” encoding=”UTF-8”?>
<Wix xmlns=”http://schemas.microsoft.com/wix/2006/wi”>
</Wix>

Product Element

This is where the characteristics of the product are defined. When a user right-clicks an MSI file and selects the properties tab, the following information is displayed:

<Wix …>
    <Product Id=”guid-here”
            Name=”Product Name”
            Language=”1033”
            Version=”1.0.0.0”
            Manufacturer=”Some Company”
            UpgradeCode=”some-other-guid-here”
    </Product>
</Wix>
  • Id represents the ProductCode property; it should always be a unique string that allows Windows to identify the target software version, and tell if it is installed on the computer. This value can be hard-coded or left as *, in which case a new GUID is generated on every compile. Since this value represents versions, it should change between different versions of the product.
  • Name is the name of the product.
  • Language is the language code of the installation. It is used to display error messages and progress information in the specified language. It is of type Microsoft LCID (decimal language ID); an antique way of classifying character sets when everything was ASCII only. The value 1033 represents English.
  • Version is the version of the product.
  • Manufacturer is the name of the company that created the product.
  • UpgradeCode is a unique identifier that is used to identify a product that is being upgraded. It is used to determine if the product is already installed on the computer, and if so, to upgrade it. This is another GUID, and it should be constant for a product line even among different product versions. It is used to identify the product across releases. Windows will use this key to keep track of all the software installed on the machine.

Package Element

This element should be nested within the Product element. It defines the characteristics of the installation package:

<Wix…>
    <Product …>
        <Package InstallerVersion=”301”
                Compressed=”yes”
                InstallScope=”perMachine”
                Manufacturer=”some company”
                Description=”Installs some software”
                Keywords=”some,keywords,here”
                Comments=”(c) 2014 some company” />
    </Product>
</Wix>
Of all the attributes given above, only Compressed is required. By setting it to `yes`, the installer will package all the MSI resources into CAB files, which will later be defined in Media or MediaTemplate elements. Technically, the `Id` attribute is also required, but by omitting it Wix will create one for us. A new one should be created every time the installer or software has changed anyway.

It should be noted that while the Product element describes the software to be installed, Package element describes the installer itself.

InstallerVersion determines the minimum versions of the Windows Installer that should be available in the target machine, as well as in the compiling machine. The exact value is calculated as 100 x major + minor, therefore version 4.05 is ‘405’.

InstallScope could be `perMachine` or `perUser`. The former means the software will be installed on "All Users" context, meaning all the users on the machine will be able to access it. As such, the installation will require Administrator privileges on a system where UAC is enabled.

MediaTemplate Element

The files to be installed are compressed into CAB files and shipped along with the installer executable. The programmer decides whether to embed or move these files separately. In WIX 3.6+, a single MediaTemplate element handles all the details, smartly splitting files to the given number of CAB files:

<Product…>
    <Package.../>
    <MediaTemplate EmbedCab=”yes” />
</Product>

The default is not to embed CAB files; Wix will create up to 999 files of 200 MB size. This size limit is changed by MaximumUncompressedMediaSize attribute, measured in MB. If the installation will be separated into several physical media, a Media element should be used. This is the ancestor of the MediaTemplate element, and should only be used if the installer will be split into multiple physical media:

<Property Id=”DiskPrompt”
    Value=”Some Software - [1]” />
<Media Id=”1”
    Cabinet=”media1.cab”
    EmbedCab=”no”
    DiskPrompt=”Disk 1”
    VolumeLabel=”Disk1” />
<Media Id=”2”
    Cabinet=”media2.cab”
    EmbedCab=”no”
    DiskPrompt=”Disk 2”
    VolumeLabel=”Disk2” />
The Property element will be used as the text in the message box the end user sees, prompting them to insert the next disk. For windows to know the correct disk, the VolumeLabel attribute must match the Volume Label of the actual disk, set during burning CD/DVD rom. Once the installer project is compiled, the MSI file and the first CAB file should be burned to the first disk. The second cab file should then be written to another disk..etc.

Directory Element

There are several directory properties provided by the Windows Installer that will be translated to their true paths during installation. Some of these are as follows:

  • AdminToolsFolder
  • AppDataFolder
  • DesktopFolder
  • PersonalFolder
  • ProgramFilesFolder
  • ProgramFiles64Folder
  • StartupFolder
  • StartMenuFolder
  • TempFolder
A nested directory structure can be defined in Wix as follows:
<Product …>
    <Package… />
    <MediaTemplate …/>
    <Directory Id=”TARGETDIR”
                Name=”SourceDir”>
        <Directory Id=”ProgramFilesFolder”>
            <Directory Id=”MyProgramDir”
                        Name=”Install Practice” />
        </Directory>
    </Directory>
</Product>

Important Note: The Directory element hierarchy has to start with a Directory element having an Id of TARGETDIR and a Name of SourceDir. These values set up the root directory of the installation; therefore they should be defined first and all other structure should be nested inside.

By default, Windows Installer will set the *TARGETDIR* to the local hard drive with the most free space, unless a target path is asked to the end user.

The Id attribute will serve as the primary key on the Directory table. If a predefined name is used (such as ProgramFilesFolder), that name should be used as the Id. Else, user-defined directories will be created during installation. The Name attribute should be supplied for a custom folder as it states the name of the directory to be created. For predefined directories, it is not necessary.

Component Element

Once the directories to be used during the installation is mapped, the next step is copying files into them. Windows Installer expects every file to be wrapped up in a component folder before it is installed; each gets its own Component element.

Each component should have a GUID, which allows Windows to track every file that gets installed on the end user's computer. During an installation, this information is stored in the registry. This lets Windows to find every piece of the product during an uninstall so that it could be completely removed. It is also used in Repair operations to replace missing files.

<Component Id=”someIdHere”
        Guid=”some-guid-comes-here”>
    <File Id=”fileIdHere”
        Source=”source.ext”
        KeyPath=”yes” />
</Component>

It is a common and suggested convention to mark a file as KeyPath, and include one file per component. This has two reasons:

  1. A KeyPath file will be replaced when it is missing, during a repair operation.
  2. In a Component element, only one file can be marked as KeyPath. Therefore if more than one file exists inside a component, only one of them could be recovered during a repair.

Adding components under directories can be done in three different ways:

  1. Component elements could be directly added into a Directory element. Although this is the simplest approach, this is the most anti-pattern way that could make the XML file tangled for a large number of components.
  2. DirectoryRef element can be used as the parent node of a Component. This approach has the advantage of keeping Directory and Component markups separate in the XML file:
    <Directory Id=”TARGETDIR”
            Name=”SourceDir”>
        <Directory Id=”ProgramFilesFolder”>
            <Directory Id=”MyProgramDir”
                    Name=”Install Practice” />
        </Directory>
    </Directory>
    
    <DirectoryRef Id=”MyProgramDir”>
        <Component …>
            <File .../>
        </Component>
    </DirectoryRef>
  3. Grouping Component elements under a ComponentGroup, and setting Directory attribute of it:
    <ComponentGroup Id=”someGroup”
        Directory=”MyProgramDir”>
    <Component …>
    <File .../>
    </Component>
    </ComponentGroup>

File Element

A file element can represent from simple text files to complex DLLs. It should be remembered to put a single file element per component, as discussed above:

<Component…>
	<File Id=”FILE_MyProgramDir_SomeAssemblyDLL”
		Name=”SomeAssembly.dll”
		Source=”SomeAssembly.dll”
		KeyPath=”yes” />
</Component>

A File element should always have the Source attribute, which defines the path to the file during compilation. Source paths can be relative or absolute. Preprocessor variables can be used within this attribute (see official documentation).

Id attribute becomes the primary key for a row in the MSI database, therefore it is preferred for them to be unique. If not set, it will match the file name.

Name attribute gives the chance to change the file name once it has been copied to the user's computer. By default, it will use the name of the source file.

KeyPath determines that the file will be replaced during a repair operation. If not set, the first file in a component will become KeyPath automatically.

Hidden sets the file's Hidden flag.

ReadOnly sets the file's ReadOnly flag.

Vital Sets whether the installation should continue or not when the file could not be copied successfully. It is true by default.

Feature Element

A Feature is a group of components that the user can decide to install together. They are often seen in installation dialogs as a list of modules, called feature tree, where each is included or excluded from the installation.

Every component must be installed in a feature. Generally, components that rely on one another that form a complete, self-sufficient unit should be grouped together as a feature. If a feature is disabled, there should not be orphaned files that are not being used.

If the product does not have any optional parts, one feature may be created. The following example creates two features:

<Feature Id="MainProduct"
    Title="Main Product"
    Level="1">
<ComponentRef Id="CMP_MyAppExe"/>
<ComponentRef Id="CMP_ReadMeText" />
<ComponentRef Id="CMP_StartMenuShortcuts" />
</Feature>
<Feature Id="OptionalTools"
    Title="Optional Tools"
    Level="1">
<ComponentRef Id="CMP_ToolsExe" />
</Feature>

Id attribute uniquely identifies the feature and should be referenced when installing from the command console using ADDLOCAL property.

Title attribute is used to supply a user-friendly name that will be displayed in the dialog.

Level attribute of 1 means the feature will be displayed as "to be installed" by default, and users can exclude it if desired. However, if Level is set to 0, the feature will be removed from the feature tree and the user will not be able to install it.

It is possible to create more complex feature trees by nesting Feature elements. Windows Installer ensures that if a parent feature is disabled, all its child features are disabled as well:

<Feature Id="MainProduct"
    Title="Main Product"
    Level="1">
<ComponentRef Id="CMP_MyAppExe"/>
<ComponentRef Id="CMP_StartMenuShortcuts" />
    <Feature Id="SubFeature1"
    Title="Documentation"
    Level="1">
    
    <ComponentRef Id="CMP_ReadMeText" />
    </Feature>
</Feature>
<Feature Id="OptionalTools"
    Title="Optional Tools"
    Level="1">
<ComponentRef Id="CMP_ToolsExe" />
</Feature>

In this code snippet, "ReadMe" file is moved to the Documentation feature. If MainProduct is disabled, it will be disabled as well, but "MainProduct" could be installed without the documentation.

It is possible to prevent users from disabling some components (such as core components of an application) by setting the feature's Absent attribute to "Disallow".

Start Menu Shortcuts

For setting shortcuts to Windows Start Menu, first its parent directory should be defined:

<Directory Id="TARGETDIR" Name="SourceDir">
    <Directory Id="ProgramFilesFolder">
    <Directory Id="MyProgramDir"
        Name="Install Practice" />
    </Directory>
    <Directory Id="ProgramMenuFolder">
    <Directory Id="MyShortcutDir"
        Name="My Company" />
    </Directory>
</Directory>

Now, a DirectoryRef element can be used together with a component to create the necessary shortcuts:

<DirectoryRef="MyShortcutDir">
    <Component Id="CMP_DocumentationShortcut"
        Guid="some-guid-comes-here">
    <Shortcut Id="DocumentationStartMenuShortcut"
        Name="My Software Documentation"
        Description="Read Documentation of My Software"
        Target="[MyProgramDir]ReadMe.txt" />
    </Component>
</DirectoryRef>

  • Id attribute defines the unique identifier for the element.
  • Name attribute defines a user-friendly name that gets displayed.
  • Description is a string that describes the shortcut and will appear when the user moves their cursor over the shortcut file.
  • Target attribute defines the path of the actual file on the user's machine. For this reason, properties that are updated as they change should be used instead of hardcoded values. In the above example, [MyProgramDir] placeholder contains the installation path defined by the user.

Important Note: Two things that should always follow a Shortcut element are RemoveFolder and RegistryValue:

  • RemoveFolder ensures that the new Start menu subdirectory will be removed during the uninstall. It uses an id attribute to uniquely identify the row in the MSI RemoveFile table, and On attribute to specify when to remove the folder.
  • RegistryValue element is required because every Component element must have a KeyPath item. Shortcuts are not allowed as keypath items, as they are technically not files. By adding a RegistryValue, we can mark this component as a keypath. The second reason of using a RegistryValue as keypath is that the shortcut created is placed under a directory specific to the current user. Windows Installer requires that keypath items should be a registry value in order to simplify uninstalling the product, when multiple users have installed it.

<DirectoryRef="MyShortcutDir">
    <Component Id="CMP_DocumentationShortcut"
        Guid="some-guid-comes-here">
    <Shortcut Id="DocumentationStartMenuShortcut"
        Name="My Software Documentation"
        Description="Read Documentation of My Software"
        Target="[MyProgramDir]ReadMe.txt" />
    <RemoveFolder Id="RemoveMyShortcutDir"
        On="Uninstall" />
    <RegistryValue Root="HKCU"
        Key="Software\Microsoft\MyCompany"
        Name="installed"
        Type="integer"
        Value="1"
        KeyPath="yes" />
    </Component>
</DirectoryRef>

Creating "Uninstall" Shortcut

Another useful shortcut is one that uninstalls the product. It will target the "msiexec.exe" program which is located in the "System" folder. The example uses System64Folder directory Id because it will map either x64 or x86 system folders depending on the system automatically:

<DirectoryRef="MyShortcutDir">
  <Component Id="CMP_DocumentationShortcut"
      Guid="some-guid-comes-here">
    <Shortcut Id="DocumentationStartMenuShortcut"
        Name="My Software Documentation"
        Description="Read Documentation of My Software"
        Target="[MyProgramDir]ReadMe.txt" />
    <Shortcut Id="UninstallShortcut"
        Name="Uninstall my software"
                Description="Uninstalls my software and all its components"
        Target="[System64Folder]msiexec.exe"
        Arguments="/x [ProductCode]" />
    <RemoveFolder Id="RemoveMyShortcutDir"
      On="Uninstall" />
    <RegistryValue Root="HKCU"
      Key="Software\Microsoft\MyCompany"
      Name="installed"
      Type="integer"
      Value="1"
      KeyPath="yes" />
  </Component>
</DirectoryRef>

Creating Empty Folders

Ordinarily, Windows Installer does not allow the creation of empty folders. However, using a CreateFolder element inside an empty component will handle this limitation:

<DirectoryRef Id=”myEmptyFolder”>
    <Component Id=”CMP_EmptyDir”
                Guid=”guid-here”
                KeyPath=”yes”>
        <CreateFolder />
    </Component>
</DirectoryRef>

Special Case Files 1: Adding Assembly Files to Global Assembly Cache (GAC)

GAC is a central repository where .NET assembly files are stored so that they can be shared by multiple applications. To install a file to GAC, the Assembly attribute of the File eleemnt should be set to .net. Even if the element is under a DirectoryRef, it will not be copied to the specified directory, though the directory will be created. To prevent this, it is a common practice is creating a dummy folder named GAC in the directory structure, and placing the File element under it.

It should be noted that any DLL that will be installed to GAC should be strongly signed.

Special CAse Files 2: Installing TrueType Fonts

To install a font to the system, TrueType attribute of the File element should be set to yes, and the source should point to the font file.

Managing Icons

For adding icons to shortcuts, an Icon element as a child to Product element is to be added. Then it will be referenced by the "Icon" attribute of the Shortcut element:

Optimizing File Copying Sequence

File, directory, and component information is held in an MSI file under their respective tables. Each file in the File table is sorted alphabetically by the Id value given to them in the File element. This means that the order of the files in the MSI file is determined by the order of the Id attributes in the File elements. Therefore, it is a good practice to sort the Id attributes in the File elements in the same order that the files will be copied to the user's computer. If it can copy all the files that belong to a certain directory at the same, then move to another, the process will be more efficient. Therefore, putting directory name to the beginning of a file Id will lead them to be listed consecutively in teh File table, making them to be installed in the same order:

  • FILE_MyProgramDir_exe
  • FILE_MyProgramDir_help
  • FILE_MyProgramDir_dll
  • FILE_MyShortcutDir_exe
  • FILE_MyShortcutDir_help

Fragments

When an installer packages hundreds of files, it is not the best practice to put all the code into a single file. Elements should be split up into multiple .wxs files for better organization and readability whereas the main source file, Product.wxs nests everything inside a Product element. These additional files will use Fragment elements as their root.

Fragment elements do not need any attributes; they are simply containers. Anything could be placed into them, such as Directory or File elements. Components can be defined in a separate file named as Components.wxs:

<?xml version……?>
    <Wix xmlns=......>
        <Fragment>
<ComponentGroup Id="MyComponentGroup" Directory=…>
        <Component Id…>
            <File Id…/>
        </Component>
        <Component Id…>
            <File Id…/>
        </Component>
        <Component Id…>
            <File Id…/>
        </Component>
</ComponentGroup>
        </Fragment>
    </Wix>

To reference this fragment in the Product.wxs file, ComponentGroupRef element is used:

<Feature Id="myFeature" Title="mainFeature Level="1">
                                      <ComponentGroupRef Id="MyComponentGroup" />
                                    </Feature>

Important Note: Once an item from a fragment is referenced in another fragment (event if a single property or icon), all the elements in the source fragment are referenced to the product. With fragments, it is all or nothing.

Elements that do not have a Ref counterpart are trickier to use. In these cases, the "all or nothing" principle is used. For example, the Media element does not have a MediaRef counterpart, but if a Property element is defined within the same fragment where the target Media element resides, referencing this dummy Property will also reference all the Media elements, too:

<?xml…?>
<Wix…>
    <Fragment>
    <Property Id="MediaProperty"
        Value="1" />
    <Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
    <Media Id="2" Cabinet="media2.cab" EmbedCab="yes" />
    <Media Id="3" Cabinet="media3.cab" EmbedCab="yes" />
    </Fragment>
</Wix>
<?xml…?>
<Wix…>
    <Product…>
    <Package… />
    <PropertyRef Id="MediaProperty" />
    </Product>
</Wix>
<Icon Id="icon.ico" SourceFile="myIcon.ico" />
  <DirectoryRef…>
    <Component…>
      <Shortcut Id="DocumentationStartMenuShortcut"
          Name="My Software Documentation"
          Description="Read Documentation of My Software"
          Target="[MyProgramDir]ReadMe.txt"
          Icon="icon.ico />
      <RemoveFolder…/>
    <RegistryValue…/>
  </Component>
</DirectoryRef>

Harvesting Files

The term "harvesting" refers to the process of automatically generating WiX markup by scanning a directory. This is useful when a large number of files need to be included in the installer. In the older versions of WiX, this was done by using the heat.exe tool. However, in WiX 3.6+, the HeatDirectory task is used in the project file to harvest files. In WiX 5.0+, we can automate the process even more using a combination of dotnet CLI and HeatWave, however, this process is not as intuitive as one would expect.

Creating an Installer Project

Once you have installed WiX and HeatWave as described in the previous section, you can create a new WiX project in Visual Studio. To do this, you right click on the solution in the Solution Explorer and select "Add" -> "New Project". In the "Add New Project" dialog, search and select MSI Installer Project. Give it a name and click "Create".

Installing Required NuGet Packages

Unlike the past versions of WiX, the new version does not come with the necessary libraries to use HeatWave; instead they are publised as individual NuGet packages. Therefore, we need to install thesde required NuGet packages. To do this, right click on the project in the Solution Explorer and select "Manage NuGet Packages". We will need the following packages for this example:

  • WixToolset.heat: For harvesting
  • WixToolset.Netfx.wixext: For checking .net version
  • WixToolset.UI.wixext: For the default installer user interfaces

Adding EULA

Create a rich text file (RTF) that contains the End User License Agreement (EULA) text. This file will be included in the installer and the user will be prompted to read and accept it before the installation begins. Add this file to the installer project. You do not need to change its compile action, as it will be included as a file in the installer.

Modifying the Installer Project File

By default, an installer project will have a Package.wxs file that contains the main WiX markup, a localization file, and several dummy files as examples. Again, unlike older versions, the contemporary WiX uses localization files rather than obsolete codepage strings. Out of these files, we only need the Package.wxs and the Package.en-us.wxl files. The rest can be deleted.

The project file itself is not a standard WiX file that is visible in the solution explorer. Instead, we need to double click on the project name itself to open the project file. This file is an XML file that contains the necessary information about the project, such as the version of the WiX toolset, the output type, and the necessary references. We will change this file significantly to create a process flow that will follow the steps below:

  1. Publish the application itself in a release configuration so that we have the files that need to be deployed to the user's computer
  2. Harvest the files in the published directory using HeatWave
  3. Include the harvested files in the installer
  4. Clear the published files

We will have full project file below, and then we will explain the important parts (which is almost all of it):

<Project Sdk="WixToolset.Sdk/5.0.1">

    <PropertyGroup>
        <ProductVersion>1.0.0.0</ProductVersion>
        <ProjectGuid>{GUID}</ProjectGuid>
        <OutputName>Application Name Installer</OutputName>
        <OutputPath>bin\$(Configuration)\</OutputPath>
        <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
    </PropertyGroup>

    <PropertyGroup>
        <SuppressPdbOutput>true</SuppressPdbOutput>
    </PropertyGroup>

    <PropertyGroup>
        <HarvestPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\DesktopClient\bin\Release\net8.0-windows\publish\win-x64'))</HarvestPath>
        <DefineConstants>HarvestPath=$(HarvestPath);SourceDir=$(HarvestPath)</DefineConstants>
    </PropertyGroup>

    <Target Name="PublishAppname" BeforeTargets="PrepareForBuild">
        <Exec Command="dotnet publish ..\Appname\Appname.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true" />
    </Target>

    <ItemGroup>
        <HarvestDirectory Include="$(HarvestPath)">
            <ComponentGroupName>MainComponents</ComponentGroupName>
            <DirectoryRefId>INSTALLFOLDER</DirectoryRefId>
            <SuppressRootDirectory>true</SuppressRootDirectory>
            <SuppressCom>true</SuppressCom>
            <SuppressRegistry>true</SuppressRegistry>
            <AutoGenerateGuids>false</AutoGenerateGuids>
            <GenerateGuidsNow>true</GenerateGuidsNow>
            <UseUniqueNames>true</UseUniqueNames>
            <PreprocessorVariable>SourceDir</PreprocessorVariable>
        </HarvestDirectory>
    </ItemGroup>

    <ItemGroup>
        <None Include="EULA.rtf" />
        <PackageReference Include="WixToolset.Heat" Version="5.0.1" />
        <PackageReference Include="WixToolset.Netfx.wixext" Version="5.0.1" />
        <PackageReference Include="WixToolset.UI.wixext" Version="5.0.1" />
    </ItemGroup>

    <Target Name="CleanupPublishDirectory" AfterTargets="Build">
        <ItemGroup>
            <FilesToDelete Include="$(HarvestPath)\**\*.pdb" />
            <DirectoriesToDelete Include="$([System.IO.Directory]::GetDirectories('$(HarvestPath)', '*', System.IO.SearchOption.AllDirectories))" />
        </ItemGroup>
        <Delete Files="@(FilesToDelete)" />
        <RemoveDir Directories="@(DirectoriesToDelete)" Condition="!Exists('%(Identity)\*')" />
    </Target>

</Project>

The file is structured as an XML document and uses the WixToolset.Sdk version 5.0.1.

The first PropertyGroup section defines basic project properties. It sets the product version to 1.0.0.0, assigns a unique project GUID, names the output as "App Installer", and specifies the output and intermediate output paths.

The second PropertyGroup contains a single property, `SuppressPdbOutput`, set to true. This prevents the generation of Program Database (PDB) files, which are typically used for debugging.

The third PropertyGroup defines the HarvestPath, which is the location of the compiled application files that will be included in the installer. It uses MSBuild's built-in properties and functions to construct the path. The DefineConstants property sets up variables that can be used throughout the project.

The PublishApp target is executed before the build process begins. It runs a dotnet publish command to create a self-contained, single-file publish of the App project for 64-bit Windows.

The first ItemGroup sets up the directory harvesting process. It uses WiX's heat tool to automatically include all files from the publish directory into the installer. Various options are set to control how the files are included and organized within the installer.

The second ItemGroup includes the EULA (End User License Agreement) file and references the necessary WiX extension packages: Heat (for directory harvesting), Netfx (for .NET Framework detection and installation), and UI (for built-in user interface dialogs).

The final Target, named "CleanupPublishDirectory", runs after the build. It removes all PDB files from the publish directory and deletes any empty directories that might be left after the build process.

Overall, this WiX project file sets up an automated process to gather all the necessary files, organize them into an installer package, and clean up any unnecessary files after the build is complete. It's designed to create a streamlined, self-contained installer for the application.

Updating Package.wxs File

We will see the full Package.wxs file below, and then we will explain the important parts (which is almost all of it, again):

<?xml version="1.0" encoding="UTF-8"?>
    <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
            xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
        <Package Name="Awesome Software"
                    Manufacturer="Awesome Co."
                    Version="1.0.0.0"
                    UpgradeCode="{GUID}">
            <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    
            <MediaTemplate EmbedCab="yes"/>
            
            <Feature Id="ProductFeature" Title="App" Level="1">
                <ComponentGroupRef Id="MainComponents" />
                <ComponentRef Id="ApplicationShortcut" />
            </Feature>
    
            <StandardDirectory Id="ProgramFiles64Folder">
                <Directory Id="INSTALLFOLDER" Name="App Folder" />
            </StandardDirectory>
    
            <StandardDirectory Id="ProgramMenuFolder">
                <Directory Id="ApplicationProgramsFolder" Name="App Folder"/>
            </StandardDirectory>
    
            <ui:WixUI Id="WixUI_Minimal" />
    
            <WixVariable Id="WixUILicenseRtf" Value="EULA.rtf" />
    
            <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
        </Package>
    
        <Fragment>
            <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
                <!-- Components will be added here by the harvester -->
            </ComponentGroup>
    
            <DirectoryRef Id="ApplicationProgramsFolder">
                <Component Id="ApplicationShortcut" Guid="*">
                    <Shortcut Id="ApplicationStartMenuShortcut"
                                Name="Awesome App"
                                Description="Start Awesome App"
                                Target="[INSTALLFOLDER]App.exe"
                                WorkingDirectory="INSTALLFOLDER"/>
                    <RemoveFolder Id="CleanUpShortCut" Directory="ApplicationProgramsFolder" On="uninstall"/>
                    <RegistryValue Root="HKCU" Key="Software\Awesome\Awesome" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
                </Component>
            </DirectoryRef>
        </Fragment>
    </Wix>

This file, named Package.wxs, is the main WiX (Windows Installer XML) source file that defines the structure and behavior of the Windows Installer package for the application.

The root element is <Wix>, which includes namespace declarations for the core WiX schema and the UI extension. This sets up the XML structure and available elements for the installer definition.

The <Package> element defines the core properties of the installer. It specifies the name, the manufacturer, the version as "1.0.0.0", and includes an UpgradeCode GUID used for managing updates to the software.

The <MajorUpgrade> element configures the behavior for upgrades, preventing downgrades by displaying an error message if a newer version is already installed.

<MediaTemplate EmbedCab="yes"> instructs the installer to embed the Cabinet (.cab) files into the main installer file, creating a single, self-contained installation package.

The <Feature> element defines a feature named "ProductFeature" that includes the main components of the application and the application shortcut. This allows for potential customization of the installation process.

Two <StandardDirectory> elements are used to define the installation directories. One specifies the installation folder within Program Files (64-bit), and the other defines the location for the Start Menu shortcut.

<ui:WixUI Id="WixUI_Minimal"> specifies the use of the minimal WiX user interface during installation, providing a streamlined installation experience.

The <WixVariable> element points to the EULA (End User License Agreement) file, which will be displayed during the installation process.

The <Property> element sets the default installation directory, allowing users to change it if desired.

In the <Fragment> section, a <ComponentGroup> is defined as a placeholder for the main application components. These will be populated by the harvester tool during the build process.

Finally, there's a <Component> definition for creating the application shortcut in the Start Menu. It includes the shortcut creation, cleanup on uninstall, and a registry entry to mark the installation.

Conclusions

In this post, we have barely scratched the surface on how to create an installer for a .NET application using WiX Toolset. We have covered the basics of WiX markup, including components, features, shortcuts, and harvesting files. We have also seen how to automate the harvesting process using HeatWave and integrate it into a WiX project.

The details and inner workings of WiX are vast and complex, and there is much more to learn and explore. Most of it is also dependent on how Windows Installer works, which is a complex system in itself. In the following appendices of this post, we will cover the inner workings of Windows Installer and how WiX Toolset interacts with it.

Appendix 1: Installation Sequence in Windows Installer

There are two tables in the MSI database; the InstallUISequence and the InstallExecuteSequence.

InstallUISequence

This is both the name of a database table in the MSI package and way of referring to the first half of the installation. During this time, a graphical user interface may be shown and tasks that do not alter the user's computer (such as AppSearch and condition evaluation) may be performed.

The standard actions and their order in an installation are:

  • FindRelatedProducts
  • AppSearch
  • LaunchConditions
  • ValidateProductID
  • CostInitialize
  • FileCost
  • CostFinalize
  • MigrateFeatureStates
  • ExecuteAction

FindRelatedProducts

It looks through a table in the MSI called Upgrade. This table lists upgrade codes, version numbers and languages that the installer uses as criteria when searching for prior versions of target software.

AppSearch

The action reads the AppSearch table, that holds the signatures of the searches authored in the WiX markup.

LaunchConditions

This action references the table called LaunchCondition that lists conditional statements that must be true before the installer can continue.

ValidateProductID

A software key may be collected from the end-user and stored in a property called "PIDKEY". During this action, this property is compared to another property called "PIDTemplate", that defines a pattern "PIDKEY" must match. If everything checks out, a third property named "ProductID" is set. After this action has run, existence of "ProductID" key may be checked to see if the key that was entered is in the valid format.

CostInitialize

This action starts "costing" process wherein the disk space needed for the product is calculated. At this stage, Component and Feature tables are loaded into memory, which sets the stage for the installer to check which components and features will be installed.

FileCost

The installer starts the cost calculations. The rows in the "File" table are examined to see how much stape they require. If one of the files already exists on the end user's system due to a prior installation of the same parent Component, it will only be replaced if the file's version is newer. In that case, the size of the file will be added to the disk space needed.

CostFinalize

In this stage, the costing calculation takes into consideration the components and features that should not be installed because of a component or a feature level condition. It then verifies that all target directories are writable. This phase ends the costing process.

MigrateFeatureStates

If the previous version of software was installed, this action checks which features were installed last time and then sets the action state of those features to the same state in the current installer. That way, the new installer will show a feature tree with the corresponding features enabled/disabled.

ExecuteAction

The last standard action in the UI sequence looks at a property called EXECUTEACTION to see which table to pass control to. As this is a normal installation that started off by reading InstallUISequence, the property will be set to "INSTALL" and this action will pass control to the InstallExecuteSequence table. For other scenarios, EXECUTEACTION may be set to ADMIN or ADVERTISE.

InstallExecuteSequence

During this phase, changes are made to the computer such as laying down files, updating the registry, and adding a new entry in Programs and Features. This part of the installation is called the server side whereas the UI part is called client side, which is a way of conceptualizing that the two are run in different sessions and with different privileges. Client side runs as the user who launched the MSI while the server side runs as the LocalSystem user.

If an installation is done as logging enabled, actions occur during first half starts with MSI ( c ) whereas the second half starts with MSI(s) :

MSI (C ) (64:80) [13:41:32:201] : Switching to server:
                                  MSI (s) (D0:4C) [13:41:32:218] : Grabbed execution mutex.

By taking ownership of the execution mutex, the server side is saying that no other MSI package can be run while the execution phase is in progress.

InstallValidate

It uses the total calculated by the costing phase to verify that there is enough disk space available, and whether any running processes have a lock on files needed by the MSI.

InstallInitialize

It marks the beginning of the deferred stage of the execute sequence. Any actions between it and InstallFinalize are included in a transaction, and can be rolled back if an error occurs.

ProcessComponents

It makes notes of the components that are in the installer and stores their guids in the registry. It tracks which file is the keypath for each component.

UnpublishFeatures

During uninstallation, it removes components-to-feature mappings in the registry and discards information about which features were selected.

RemoveRegistryValues

It looks at the MSI's Registry and RemoveRegistry tables to find registry items to remove during an uninstall.

RemoveShortcuts

It removes any shortcuts during uninstallation that the installer created.

RemoveFiles

During uninstallation, it deletes files and folders that were copied to the system.

InstallFiles

It uses information from Directory and File tables to copy files and folders into their appropriate locations.

CreateShortcuts

It adds shortcuts as specified in the Shortcut table.

WriteRegistryValues

It does the registry writings stated in the WiX markup.

RegisterUser

It records to the registry who the user was who initiated the installation

RegisterProduct

It registers the product with Programs and Features and stores a copy of the MSI package in the Windows Installer Cache, found at %WINDIR%/Installer :

PublishFeatures

The installed state (installed, advertised or absent) is written to the registry and components are mapped into features.

PublishProduct

Used only by an advertised installation, it publishes the product to a computer, i.e. making it available to be installed on-demand by non-administrator users.

InstallFinalize

It marks the end of the roll-back protected stage called the deferred phase. If an installation gets this far, it is successful.

Immediate vs Deferred

The main reason for dividing an installation into two phases (UI and Execution) is supplying rollback if an error occurs. All actions in the execution phase, between InstallInitialize and InstallFinalize are included in the roll-back, which is called the deferred stage. The initial phase, where the roll-back script is being prepared but the roll-back protection has not started yet, is called the immediate stage.

The UI sequence does not have any roll-back feature, therefore actions that alter the system should never take place there.

Custom actions that make system changes should be marked as deferred and should be scheduled to run between InstallInitialize and InstallFinalize in the execution phase. Roll-back actions should be supplied by the developer for custom actions.

Custom Actions

As stated, any custom action that changes the system should happen during the deferred stage of execution sequence. Otherwise, they could be put in anywhere.

There are seven specific types of custom actions that add their own necessary attributes.

<CustomAction Id="myaction" Execute="deferred" Return="check" />
    <InstallUISequence>
    <Custom Action="myaction" After="CostFinalize" />
    <Custom Action="myaction2" After="myaction" />
    </InstallUISequence>
    <InstallExecuteSequence>
    <Custom Action="myaction3" After="InstallInitialize" />
    <Custom Action="myaction4" After="myaction3" />
    </InstallExecuteSequence>

The "return" attribute tells the installer whether it should wait for the custom action to complete its processing before continuing, and whether the return code should be evaluated:

  • asyncNoWait: The custom action will run asynchronously and may continue after the installer terminates.
  • asyncWait: The custom action will run asynchronously and will wait for the return code at sequence end.
  • check: The custom action will run synchronously and the return code will be checked for success. This option is the default value.
  • ignore: The custom action will run synchronously and the return code will not be checked.

Setting a Windows Installer Property

Type 51 custom actions set properties during the installation. The type numbers come from the Type column in the CustomAction table.

Setting Location of an Installed Directory

Type 35 custom actions sets the path of a directory element.

Running Embedded VBScript or JScript

Type 37 (JScript) or Type 38 (VBScript) custom actions execute embedded scripts.

Calling External VBScript or JScript File

Type 5 (JScript) or Type 6 (VBScript) custom actions call a subroutine or function from an external script file.

Calling A Function from a Dynamic-Link Library

Type 1 custom action calls a method from a dynamic-link library (.dll). The Votive plugin for Visual Studio provides a template for this purpose.

The custom action library will be written using .NET code in C#. Technically, Type 1 custom action means authoring an unmanaged C/C++ DLL; Windows Installer cannot natively support .NET custom action. The template used allows to write a managed code that will build the C/C++ DLL when compiled. This unmanaged code will wrap the .NET code. This approach requires that end user should have target .NET framework. If this is not an option, WiX provides a template for C++ custom action.

There is a cap in total number of Custom Actions that can be defined in a single .NET assembly; 128 for WiX 3.6 and 16 for previous versions.

The code in the custom action library will use Deployment Tools Foundation (DTF) that allows it to interact with the lower-level Windows Installer technology.

When the library project is compiled, two outputs will be generated; a .dll and a .CA.dll file. The second one contains the unmanaged code and should be referenced in WiX markup.

Triggering an Executable

There are three different ways to trigger an exe file during an installation.

  • Type 2 action uses a Binary element to store the file inside MSI and calls it from there; as a result it does not need to be copied to the user's computer. The Impersonate attribute of this element tells the installer whether to impersonate the user who launched the installer. Default is no; meaning that the custom action should run as the LocalSystem, an all-powerful build-in account that has all privileges. If this is not necessary, setting Impersonate to yes will run the custom action in context of the current user. This flag is used for deferred custom actions only; immediate custom actions do not run as LocalSystem and an ICE68 error will be thrown if Impersonate is set to no on one.
  • Type 18 custom actions copies an executable to the user's computer and then runs it.
  • Type 34 custom actions run an executable that is already on the user's computer. The ExeCommand attribute should reference the target directory and the name of the .exe file, including arguments.

Sending an Error that Stops the Installation

Type 19 custom action sends an error to the installer and ends it. This action can only be run during the immediate phase, therefore Execute attribute is unnecessary.

Rolling Back Custom Actions

Custom actions that are scheduled are deferred during the Execute sequence's rollback-protected phase. To give those actions rollback capabilities, separate custom actions that undo the work should be authored.

Rollback actions are scheduled before the action they are meant to revert in case of an error.

Appendix 2: Properties and AppSearch

Properties are the variables that store any temporary data during an install. As such, they provide definitions for various predefined and custom-made installer settings, store input from the user, and provide a means of transferring information from one task to another. Additionally they can store the results of searching the end-user's computer for files, directories, registry keys and settings.

Properties are not just a feature of WiX, but are innate to Windows Installer itself.

Declaring and Setting Properties

Property ids should start with either a letter or underscore and consist of only lower and uppercase letters, numbers, underscores, and periods.

<Property Id="myProperty" value="my value" />

When referencing, property ids are case sensitive. Property values could be any string. If not set, they will be set to null and will be left out of the MSI package as if never declared.

Properties with fully uppercase names are public. Public properties persist through the entire installation whereas private properties only last during the current session.

Referencing Properties

One of the common uses of properties is to reference them in another WiX element. There is a limited list of elements that can reference a property, including the following:

  • Control.Text
  • ListItem.Text
  • Dialog.Title
  • Shortcut.Title, Arguments, Description
  • Condition.Message
  • RegistryValue.Name, Value

For referring to properties, square brackets are used: [USERNAME].

Predefined Windows Installer Properties

Implied Properties

These properties are created during an installation, but not with a Property element. They are implied from the execution of the installation. The code

<Product Id="guid-here"
           Name="Product Name"
           Language="1033"
           Version="1.0.0.0"
           Manufacturer="Some Company"
           UpgradeCode="some-other-guid-here"
</Product>

creates the following properties:

  • ProductCode
  • ProductName
  • ProductLanguage
  • ProductVersion
  • Manufacturer
  • UpgradeCode

Another set of implied properties are predefined directories. Additionally, any directory created by Directory element is available as property.

Another set of implied properties are those that guide how Windows Installer does its job. For example, "Installed" property tells the product is already installed.

Cited Properties

Most of the properties that are built into Windows Installer are not implied; they should be set explicitly with a Property element. They are different from user defined properties such that their ID must match the predefined name and they are generally used to toggle various Windows Installer settings. Complete list can be found here.

InstallLevel Property

It serves an important function; every feature's level is compared to this number. If the level is less than or equal to INSTALLLEVEL, but greater than zero, it will be enabled. Its default value is 1, but could be changed by a property, or a custom action (or from command line).

This could be used to distinguish "Full" and "Typical" installations; settings level of "Full" to 100 and "Typical" to 50, resulting in installation of all components in a "Full" install but some of them in a "Typical".

AppSearch

Windows Installer lets you search the computer during an install for specific files, directories, and settings. Collectively, these fall under the category of AppSearch, which is the name of the MSI database table where search tasks are stored. There are five types of searches:

  • DirectorySearch
  • FileSearch
  • ComponentSearch
  • RegistrySearch
  • IniFileSearch

Each of these types refer to the WiX element to be used for a search. Each is the child of a Property element, whose value will be set to the result of the search.

DirectorySearch

As all searches, it is nested in a Property element:

<Property Id="NPP_Path">
    <DirectorySearch Path="C:\Program Files\Notepad++"
                 Depth="0"
                 AssignToProperty="yes"
                 Id="nppFolderSearch" />
</Property>

This search has an absolute path, therefore has little to no actual value. A more comprehensive search would be:

<Property Id="NPP_Path">
    <DirectorySearch Path="Notepad++"
                 Depth="5"
                 AssignToProperty="yes"
                 Id="nppFolderSearch" />
</Property>

The latter search is better; it will search for folder Notepad++ by drilling down to 5 subfolders in all available drives and directories. However, this will cause a waiting time. Worse, in Vista, Win7 or Win8, an error will be thrown while searching through hidden, restricted junction points such as C:\Documents and Settings.

In these cases, nesting DirectorySearch elements inside one another to tell Windows Installer where to search is a better solution:

<Property Id="NPP_Path">
    <DirectorySearch Path="[ProgramFilesFolder]"
                 Depth="0"
                 AssignToProperty="no"
                 Id="ProgramFilesFolderSearch" >
        <DirectorySearch Path="Notepad++"
                     Depth="5"
                     AssignToProperty="yes"
                     Id="nppFolderSearch" />
    </DirectorySearch>
</Property>

The last example searches the Program Files folder, does not assign its result to the parent property, then searches Notepad++ folder inside the result of the outer search.

As an example, three DirectorySearch could be nested to find the "Plugins" folder of notepad++.

"Path" attribute could accept:

  • WiX properties
  • Windows shares (\\myshare\myFolder)
  • relative paths
  • absolute paths
  • folder names
  • environmental variables in the form of Wix preprocessor syntax ($(env.ALLUSERSPROFILE))

If the installer cannot find the path set in the parent element, it will skip it and use the default - every attached drive's root directory.

Each DirectorySearch element must get its unique ID; Windows Installer uses these Id attributes to tie all of the elements together into one cohesive search.

FileSearch

FileSearch element is used within a DirectorySearch element, which tells the installer that a file is searched within the stated directory:

<Property Id="README_FILE">
    <DirectorySearch Path="..."
                 Depth="0"
                 AssignToProperty="no"
                 Id="NppSearch" >
        <FileSearch Name="readme.txt"
                Id="readmeFileSearch" />
    </DirectorySearch>
</Property>

If found, the property will be populated with the absolute path to the target file.

The FileSearch element cannot do recursive searches through subfolders. Therefore its parent DirectorySearch element must point out the exact folder where the file is supposed to be, if it specifies a path with the "Path" attribute. If no path is provided, the installer will search the file in all the attached directories.

Other attributes could be added to the FileSearch element to refine the search, such as "MinSize", "MaxSize", "MinDate", "MaxDate", "MinVersion", "MaxVersion"...etc. The date filters must use the time format "YYYY-MM-DDTHH:mm:ss" (e.g. 2009-12-20T12:30:00).

ComponentSearch

A second way to search files installed on the end-user's computer is to use a ComponentSearch element. The result will not be the full path, but close to it. To do component searches, however, the GUID of the installed file's component should be known.

ComponentSearch has several uses. First of all, it could be used to determine where the user has installed the target software as it returns the absolute path to the directory where the specified file is. Secondly, other softwares could also be checked, assuming their component guids are known.

RegistrySearch

It allows reading values from windows registry. It could be combined with FileSearch and DirectorySearch by the "Type" attribute. It could be "directory", "file", and "raw".

For example, the following code gets path of a file from registry and checks for the file:

<Property Id="MY_PROPERTY">
  <RegistrySearch Id="myRegSearch"
            Root="HKLM"
            Key="Software\WIXTEST"
            Name="PathToFile"
            Type="file" >
    <FileSearch id="myFileSearch" Name="[MY_PROPERTY]" />
  </RegistrySearch>
</Property>

Here, the RegistrySearch element finds the item in the registry and sets the value of MY_PROPERTY. Next, the nested FileSearch element can now read that property and use it to find the file on the computer. If it finds the file, it replaces the value of the property with the full path of the file - which should be the same as before. If not, its value is set to null.

In order for this to work, the type of RegistrySearch should be set to "File". This tells the installer that it should expect to find the path of a file in the registry and it is intended to nest a FileSearch element inside the RegistrySearch element.

The same approach is also valid for DirectorySearch.

Setting type to "Raw" allows you to read the registry value and set it to property, but nothing more. It should be noted that Windows Installer will add additional characters to distinguish different data types:

  • DWORD: Starts with "#", optionally followed by "+" or "-"
  • REG_BINARY: Starts with "#x" and the installer converts and saves each hexadecimal digit as an ASCII character prefixed by "#x"
  • REG_EXPAND_SZ: Starts with "#%"
  • REG_MULTI_SZ: Starts with "[~]" and ends with "[~]"
  • REG_SZ: No prefix, but if the first character of the registry value is "#" the installer escapes the character by prefixing it with another "#".

Registry searches are represented in the MSI database on the RegLocator table. The Id attribute is listed under Signature column and is referenced by various other tables including AppSearch, DrLocator, and Signature. For complex searches where FileSearch, DirectorySearch, elements are nested into RegistrySearch, these tables are joined together by this ID.

IniFileSearch

It lets you search INI configuration files for settings.

Appendix 3: Launch Conditions and Installed States

Conditions

There are several types of conditions and all use the Condition element to house their logic. The meaning of this element changes depending on where it's placed relative to other elements and which attributes it uses.

  • Launch Conditions: check for prerequisites at the beginning of the installation and prevent it from continuing if their requirements are not met. They could be placed anywhere inside either the Product element in main .wxs file or a Fragment element in a separate file.
  • Feature Conditions & Component Conditions: They are child elements to Component or Feature elements. Both prevent a specific feature or component from being installed if a condition is not satisfied.

Condition Syntax

Conditions contain statements that evaluate to either true or false. In most cases a property is compared to some value.

Conditional statements are placed into Condition elements, and it is a good idea to always place them inside CDATA tags so that XML parser will not mistake them for XML elements:

<Condition…>
    <![CDATA[PropertyA < PropertyB]]>
</Condition>

Launch Conditions

MSI database for an installer has a table called LaunchConditions that lists rules that the end user must comply with in order to install the software. This table is evaluated early on, right after AppSearch is performed.

It should be noted that even though a large number of conditions can be defined, their evaluation order cannot be determined. Therefore, evaluation assessment planning should be done inconsequential.

Some predefined conditions exist in several WiX extensions such as WixNetFxExtension.

The following example checks whether .NET 4.0 has been installed:

<PropertyRef Id="NETFRAMEWORK40FULL" />
<Condition Message="Install .NET Framework 4.0+"
    <![CDATA[Installed or NETFRAMEWORK40FULL]]>
</Condition>

Since the property is only set when that version of .NET is installed, checking only for its existence is enough. The property is pulled to scope using a PropertyRef element, since in the extension it is defined inside a Fragment element.

In addition, WixPSExtension defines properties for PowerShell versions.

Windows Installer provides many built-in properties such as "VersionNT" which implies the operating system:

<Condition Message="OS Must be Vista or higher">
                                <![CDATA[Installed or VersionNT>=600]]>
                            </Condition>

Environmental variables could also be used in conditional statements. The following example checks there should be at least two processors in the computer:

<Condition Message="At least 2 processors needed, you have [%NUMBER_OF_PROCESSORS]"
    <![CDATA[Installed or %NUMBER_OF_PROCESSORS>=2]]>
</Condition>

Condition elements could be nested inside the Product element in the main .wxs file. For greater modularity, they could be put into separate files within Fragment elements, than be referenced in the main file using a PropertyRef:

Fragment File:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
    <Property Id="LaunchConditionsFile" Value="1" />
    <Condition Message="OS Must be Vista or higher">
        <![CDATA[Installed or VersionNT>=600]]>
</Condition>
</Fragment>
</Wix>

Main File:

<PropertyRef Id="LaunchConditionsFile" />

Feature Conditions

These conditions are nested inside Feature elements and determines whether a feature can be installed or not.

They work by changing the "Level" attribute of the parent Feature element. Every feature has a level, which is a number that tells the installer whether or not this feature should be "on". In simple setups, "1" will include the feature and "0" will exclude it.

It should be noted that when a feature level is changed to zero by a condition, it is not just disabled, but removed from the list shown in the user interface:

<Feature Id="MainFeature"
        Title="Main Feature"
        Level="1">
    <ComponentRef Id="someId" />
    <Condition Level="0">
        <![CDATA[NOT REMOVE = "ALL" AND MyProperty = "someValue"]]>
    </Condition>
</Feature>

Component Conditions

Similar to feature conditions, they only affect a single component. They allow much more granular decisions, such as if a user does not have powershell installed, a component that installs powershell could be disabled and one that installs CMD shell script could be enabled.

Action States

The "Level" attribute, behind the scenes, sets the action state of the feature. It is the object that stores whether or not the end user has requested that the feature be installed (true for components as well). It can have any of the following values:

  • Unknown(-1): state is unknown, mostly because of costing has not taken place.
  • Advertised(1): Feature will be installed advertised; it will be installed on demand. This does not exist for components.
  • Absent(2): Feature/Component will not be installed.
  • Local(3): Feature/Component will be installed to local hard disk.
  • Source(4): Feature/Component will be run from source.