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
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
<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:
- A KeyPath file will be replaced when it is missing, during a repair operation.
- 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:
Component
elements could be directly added into aDirectory
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.DirectoryRef
element can be used as the parent node of aComponent
. This approach has the advantage of keepingDirectory
andComponent
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>
- Grouping
Component
elements under aComponentGroup
, 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
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
Special CAse Files 2: Installing TrueType Fonts
To install a font to the system, TrueType
attribute of the
File
element should be set to
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 File
element. This means that the order
of the files in the MSI file is determined by the order of the File
elements. Therefore, it is a good practice to sort 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
- 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:
- Publish the application itself in a release configuration so that we have the files that need to be deployed to the user's computer
- Harvest the files in the published directory using HeatWave
- Include the harvested files in the installer
- 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 isno
; meaning that the custom action should run as theLocalSystem
, an all-powerful build-in account that has all privileges. If this is not necessary, settingImpersonate
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.