WiX 3 Tutorial 1: Solution/Project structure and Dev resources
This is the first post about building a WiX 3 installer from zero. The reason I’ve decided to write this WiX series is that the good sources are quite hard to find and examples even harder.
The finished installer (at the end of the series) will be localized in 2 languages, have localized EULA check, product key validation via a custom action, updating (and killing the running app when updating) and wrapping both localized MSI’s into a bootstrapper that will enable you to choose the installation language and install prerequisites like .Net framework 3.5. Those are the general features and we’ll well dive into each feature in a separate post.
We’ll be using WiX 3.0.5419.0 in Visual Studio 2008. You can download WiX at theSourceForge Wix Site. I’m expecting that you’re familiar with basic WiX operations and how to make the WiX project.
To help you with your WiX development be sure to use the ORCA tool (download viaWin SDK or directly fromhere) for viewing/editing MSI tables and defaultMSI logging capabilities like “msiexec /i SuperForm.Installer.msi /l*v SuperFormLog.txt” which will write a very detailed log of what it’s doing at each step.
The application we’ll be installing is called SuperForm which solves all your label’s text color needs by using the awesome action of pressing a button.
The first thing we have to do is to create a new System Environment variable SuperFormFilesDir containing the path to the directory where you keep the files you’ll install on the users computer. We’ll be using this variable in the automated building of wxs fragment file that holds the correct directory/files structure. This is my preference in environments with multiple developers. It is highly unlikely that all developers have the same directory structure. If you work alone you can also take the “preprocessor variables” route described below in Project Properties –> Build. I’ll be using the System Environment variable approach.
We have three projects of which the most important for us is the WiX project. Logically the project is divided into 7 folders and 1 main file.
We also need to include some references:
All non WiX files (others than wsx, wxi, wxl) don’t really need to be included in the project but i like to include them for clarity. This way we see exactly what is in the installer from the project itself.
WiX 3 Tutorial 2: Understanding main WXS and WXI file
In the previous post we’ve taken a look at the WiX solution/project structure and project properties. We’re still playing with our super SuperForm application and today we’ll take a look at the general parts of the main wxs file, SuperForm.wxs, and the wxi include file. For wxs file we’ll just go over the general description of what each part does in the code comments. The more detailed descriptions will be in future posts about features themselves.
Include files are exactly what their name implies. To include a wxi file into the wxs file you have to put the wxi at the beginning ofeach .wxs file you wish to include it in. If you’ve ever worked with C++ you can think of the include files as .h files. For example if you include SuperFormVariables.wxi into the SuperForm.wxs, the variables in the wxi won’t be seen in FilesFragment.wxs or RegistryFragment.wxs. You’d have to include it manually into those two wxs files too.
For preprocessor variable $(var.VariableName) to be seen by every file in the project you have to include them in the WiX project properties->Build->“Define preprocessor variables” textbox.
This is why I’ve chosen not to go this route because in multi developer teams not everyone has the same directory structure and having a single variable would mean each developer would have to checkout the wixproj file to edit the variable. This is pretty much unacceptable by my standards. This is why we’ve added a System Environment variable named SuperFormFilesDir as is shown in the previousWix Tutorial post. Because the FilesFragment.wxs is autogenerated on every project build we don’t want to change it manually each time by adding the include wxi at the beginning of the file. This way we couldn’t recreate it in each pre-build event.
xml version="1.0" encoding="utf-8"?>
<Include>
define MajorVersion="1" ?>
define MinorVersion="0" ?>
define BuildVersion="0" ?>
define Revision="0" ?>
define VersionNumber="$(var.MajorVersion).$(var.MinorVersion).$(var.BuildVersion).$(var.Revision)" ?>
define UpgradeCode="YOUR-GUID-HERE" ?>
define ResourcesDir="$(var.ProjectDir)\Resources" ?>
define ExeProcessName="SuperForm.MainApp.exe" ?>
Include>
For now there’s no way to tell WiX in Visual Studio to have a wxi include file available to the whole project, so you have to include it in each file separately.
Only variables set in “Define preprocessor variables” or System Environment variables are accessible to the whole project for now.
We’ll only take a look at the general structure of the main SuperForm.wxs and not its the details. We’ll cover the details in future posts. The code comments should provide plenty info about what each part does in general.
Basically there are 5 major parts. The update part, the conditions and actions part, the UI install sequence, the directory structure and the features we want to include.
xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
include $(sys.CURRENTDIR)Includes\SuperFormVariables.wxi ?>
<Product Id="*" Name="!(loc.ProductName) $(var.VersionNumber)" Language="!(loc.LANG)" Version="$(var.VersionNumber)" Manufacturer="!(loc.ManufacturerName)" UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="300" Compressed="yes" InstallScope="perMachine"/>
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Upgrade Id="$(var.UpgradeCode)">
<UpgradeVersion OnlyDetect="yes" Minimum="$(var.VersionNumber)" IncludeMinimum="no" Property="NEWER_VERSION_FOUND" />
<UpgradeVersion Minimum="0.0.0.0" IncludeMinimum="yes" Maximum="$(var.VersionNumber)" IncludeMaximum="no" Property="OLDER_VERSION_FOUND" />
Upgrade>
<PropertyRef Id="NETFRAMEWORK35"/>
<Condition Message="!(loc.DotNetFrameworkNeeded)">
[CDATA[Installed OR NETFRAMEWORK35]]>
Condition>
<Condition Message="!(loc.AppNotSupported)">
[CDATA[Installed OR ((VersionNT >= 501 AND ServicePackLevel >= 2) OR (VersionNT >= 502))]]>
Condition>
<CustomAction Id="NewerVersionFound" Error="!(loc.SuperFormNewerVersionInstalled)" />
<InstallExecuteSequence>
<Custom Action="NewerVersionFound" After="FindRelatedProducts">
[CDATA[NEWER_VERSION_FOUND]]>
Custom>
<RemoveExistingProducts After="InstallInitialize"/>
<Custom Action="WixCloseApplications" Before="InstallInitialize" />
InstallExecuteSequence>
<util:CloseApplication Id="CloseSuperForm" CloseMessage="no" Description="!(loc.MustCloseSuperForm)"
ElevatedCloseMessage="no" RebootPrompt="no" Target="$(var.ExeProcessName)" />
<UIRef Id="WixUI_InstallDir" />
<UI>
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
UI>
<Icon Id="SuperFormIcon.ico" SourceFile="$(var.ResourcesDir)\Exclam.ico" />
<Property Id="ARPPRODUCTICON" Value="SuperFormIcon.ico" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="!(loc.ProductName)" />
Directory>
Directory>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
<Feature Id="SuperFormFeature" Title="!(loc.ProductName)" Level="1">
<ComponentGroupRef Id="SuperFormFiles" />
<ComponentRef Id="cmpVersionInRegistry" />
<ComponentRef Id="cmpIsThisUpdateInRegistry" />
Feature>
Product>
Wix>
For more info on what certain attributes mean you should look into the WiX Documentation.
WiX 3 Tutorial 3: Generating file/directory fragments with Heat.exe
In previous posts I’ve shown you our SuperForm test application solution structure and how themain wxs and wxi include file look like. In this post I’ll show you how to automate inclusion of files to install into your build process. For our SuperForm application we have a single exe to install. But in the real world we have 10s or 100s of different files from dll’s to resource files like pictures. It all depends on what kind of application you’re building. Writing a directory structure for so many files by hand is out of the question. What we need is an automated way to create this structure. Enter Heat.exe.
Heat is a command line utility to harvest a file, directory, Visual Studio project, IIS website or performance counters. You might ask what harvesting means? Harvesting is converting a source (file, directory, …) into a component structure saved in a WiX fragment (a wxs) file.
There are 2 options you can use:
There is no perfect solution so pick one and deal with it. I prefer using the second way. A neat way of overcoming the con of the second option is to have a post-build event on your main application project (SuperForm.MainApp in our case) to copy the files needed to be installed in a special location and have the Heat.exe read them from there. I haven’t set this up for this tutorial and I’m simply including all files from the default SuperForm.MainApp \bin directory.
Remember how we created a System Environment variable called SuperFormFilesDir? This is where we’ll use it for the first time. The command line text that you have to put into the pre-build event of your WiX project looks like this:
"$(WIX)bin\heat.exe" dir "$(SuperFormFilesDir)" -cg SuperFormFiles -gg -scom -sreg -sfrag -srd -dr INSTALLLOCATION -var env.SuperFormFilesDir -out "$(ProjectDir)Fragments\FilesFragment.wxs"
After you install WiX you’ll get the WIX environment variable. In the pre/post-build events environment variables are referenced like this: $(WIX). By using this you don’t have to think about the installation path of the WiX. Remember: for 32 bit applications Program files folder is named differently between 32 and 64 bit systems. $(ProjectDir) is obviously the path to your project and is a Visual Studio built in variable.
You can view all Heat.exe options by running it without parameters but I’ll explain some that stick out the most.
If you have source control you have to include the FilesFragment.wxs into your project but remove its source control binding. The auto generated FilesFragment.wxs for our test app looks like this:
xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<ComponentGroup Id="SuperFormFiles">
<ComponentRef Id="cmp5BB40DB822CAA7C5295227894A07502E" />
<ComponentRef Id="cmpCFD331F5E0E471FC42A1334A1098E144" />
<ComponentRef Id="cmp4614DD03D8974B7C1FC39E7B82F19574" />
<ComponentRef Id="cmpDF166522884E2454382277128BD866EC" />
ComponentGroup>
Fragment>
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<Component Id="cmp5BB40DB822CAA7C5295227894A07502E" Guid="{117E3352-2F0C-4E19-AD96-03D354751B8D}">
<File Id="filDCA561ABF8964292B6BC0D0726E8EFAD" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.exe" />
Component>
<Component Id="cmpCFD331F5E0E471FC42A1334A1098E144" Guid="{369A2347-97DD-45CA-A4D1-62BB706EA329}">
<File Id="filA9BE65B2AB60F3CE41105364EDE33D27" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.pdb" />
Component>
<Component Id="cmp4614DD03D8974B7C1FC39E7B82F19574" Guid="{3443EBE2-168F-4380-BC41-26D71A0DB1C7}">
<File Id="fil5102E75B91F3DAFA6F70DA57F4C126ED" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.vshost.exe" />
Component>
<Component Id="cmpDF166522884E2454382277128BD866EC" Guid="{0C0F3D18-56EB-41FE-B0BD-FD2C131572DB}">
<File Id="filF7CA5083B4997E1DEC435554423E675C" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.vshost.exe.manifest" />
Component>
DirectoryRef>
Fragment>
Wix>
The $(env.SuperFormFilesDir) will be replaced at build time with the directory where the files to be installed are located. There is nothing too complicated about this. In the end it turns out that this sort of automation is great!
There are a few other ways that Heat.exe can compose the wxs file but this is the one I prefer. It just seems the clearest. Play with its options to see what can it do. It’s one awesome little tool.
The WiX project supports adding project references to other projects such as VB and C#. This ensures that build order dependencies are defined correctly within the solution. In addition, it generates a set of WiX preprocessor variables that can be referenced in WiX source files and preprocessor definitions which are passed to the compiler at build time.
To add a project reference to a WiX project:
Once a project reference is added, a list of project variables becomes avaliable to be referenced in the WiX source code. Project reference variables are useful when you do not want to have hard-coded values. For example, the $(var.MyProject.ProjectName) variable will query the correct project name at build time even if I change the name of the referenced project after the reference is added.
The following demonstrates how to use project reference variables in WiX source code:
The WiX project supports the following project reference variables:
Variable name |
Example usage |
Example value |
var.ProjectName.Configuration |
$(var.MyProject.Configuration) |
Debug or Release |
var.ProjectName.FullConfiguration |
$(var.MyProject.FullConfiguration) |
Debug|AnyCPU |
var.ProjectName.Platform |
$(var.MyProject.Platform) |
AnyCPU, Win32, x64 or ia64 |
var.ProjectName.ProjectDir |
$(var.MyProject.ProjectDir) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\ |
var.ProjectName.ProjectExt |
$(var.MyProject.ProjectExt) |
.csproj |
var.ProjectName.ProjectFileName |
$(var.MyProject.ProjectFileName) |
MyProject.csproj |
var.ProjectName.ProjectName |
$(var.MyProject.ProjectName) |
MyProject |
var.ProjectName.ProjectPath |
$(var.MyProject.ProjectPath) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\MyApp.csproj |
var.ProjectName.TargetDir |
$(var.MyProject.TargetDir) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\bin\Debug\ |
var.ProjectName.TargetExt |
$(var.MyProject.TargetExt) |
.exe |
var.ProjectName.TargetFileName |
$(var.MyProject.TargetFileName) |
MyProject.exe |
var.ProjectName.TargetName |
$(var.MyProject.TargetName) |
MyProject |
var.ProjectName.TargetPath |
$(var.MyProject.TargetPath) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\bin\Debug\MyProject.exe |
var.ProjectName.Culture.TargetPath |
$(var.MyProject.en-US.TargetPath) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\bin\Debug\en-US\MyProject.msm |
var.SolutionDir |
$(var.SolutionDir) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MySolution\ |
var.SolutionExt |
$(var.SolutionExt) |
.sln |
var.SolutionFileName |
$(var.SolutionFileName) |
MySolution.sln |
var.SolutionName |
$(var.SolutionName) |
MySolution |
var.SolutionPath |
$(var.SolutionPath) |
C:\users\myusername\Documents\Visual Studio 2010\Projects\MySolution\MySolution.sln |
Note: var.ProjectName.Culture.TargetPath is only available for projects that have multiple localized outputs (e.g. MSMs).