For a side project I am busy with I had to create a Windows installer to distribute the software to user's computers.
I investigated several options and decided to use WiX Toolset because it is open source and there is a large community using it and interacting in a mailing list.
I had several problems creating a relatively simple installer therefore I thought it would be useful to share this in a blog post.
The following sections describe how to create an installer from scratch. The complete source code can be downloaded from my GitHub repository.
Install the Wix Toolset package
Before you can start creating the installer, you have to download the WiX Toolset installer and run it in your machine. This will bring all necessary assemblies and resources needed and will also enable you to create Wix Visual Studio projects.
Navigate to the WiX Toolset official site, download the latest version (3.8 at the time of writing) and run it in your machine.
After you have done this you are ready to start coding!
Create the Visual Studio solution
Fire up Microsoft Visual Studio and create a new project of type Setup Project.
The WiX project is provisioned with a file Product.wxs. This is were we are going to add our installer logic.
I did not mention before but WiX uses XML to configure the installers with all XML being added to *.wxs files. Custom actions can also be added and are written in C#, but we will not write any for this post.
We also need to reference a WiX assembly in our project.
Just add a reference to the assembly WixUIExtension.dll located in your WiX installation folder, typically in C:\Program Files (x86)\WiX Toolset v3.8\bin.
Configuring the installation package
The following paragraphs describe the configuration of the package to be installed. Keep in mind the organization of this project is what made sense to me, it is not a prescribed recipe from the WiX team.
With the disclaimer out of the way, let's start putting the installer together.
The first thing I did was to bring the software to install into the project, although it is not necessary. The goal of the installer is to install a software package that contains two sub-folders and some files in those sub-folders.
Our software will have two features. SubFolder1 corresponds to a feature and SubFolder2 corresponds to another feature. Later on we will give the user the possibility to choose which feature(s) to install during the installation process.
Let us start by opening the Product.wxs file and making some small changes.
- The Id of the product, so that the Windows Installer can detect newer version of our software
- The Manufacturer of the product, to identify the company producing the software being installed
- The name of the software in the error message
Now we have to decide which type of installer we need. WiX provides several templates out of the box but we can also create a totally customized installation experience. I'm going to use the Advanced template because it provides Feature selection but in the end it is as good as any other for this sample.
To configure the Advanced template we just have to add the following line to our Product.wxs (full code sample further down):
<UIRef Id="WixUI_Advanced" />
This template, when declared, uses several variables that must be configured. Let us also add them:
<WixVariable Id="WixUIBannerBmp" Value="StaticFiles\banner.png"/> <WixVariable Id="WixUIDialogBmp" Value="StaticFiles\background.png"/> <WixVariable Id="WixUILicenseRtf" Value="StaticFiles\license.rtf" />
These variables refer to files that should be added to the project because the template we chose to use depends on them.
- WixUIBannerBmp: a 493x58 pixel image that is displayed as a header of the installer
- WixUIDialogBmp: a 493x312 pixel image that is displayed as a background of the installer
- WixUILicenseRtf: an rtf file that contains the licensing details of our software and to which the user will have to give his acceptance during the installation process.
Apart from adding the variables to the wxs file, do not forget to add the three files to your project in a folder named StaticFiles (can be named something else, as long as it is consistent with the value defined in the variables).
Let's now set a couple more properties:
<Property Id="ApplicationFolderName" Value="WixSampleInstaller" /> <Property Id="WixAppFolder" Value="WixPerMachineFolder" />
- ApplicationFolderName: The folder name where the application will be installed
- WixAppFolder: Set value WixPerMachineFolder for setting a checkbox by default for installing the application for all users of the machine, or set value WixPerUserFolder to set the default for installing only for the current user
I mentioned above we want to have the users of our installer choosing which feature(s) to install.
For doing this we declare the features in the XML:
<Feature Id="SubFolder1Feature" Title="SubFolder1 Feature" Level="1"> <ComponentGroupRef Id="SubFolder1Components"/> </Feature> <Feature Id="SubFolder2Feature" Title="SubFolder2 Feature" Level="1"> <ComponentGroupRef Id="SubFolder2Components"/> </Feature>
We are declaring two features, assigning them unique identifiers, setting the title that will be displayed in the installer and adding a ComponentGroupRef that links to another section of the XML where the files part of that feature are declared. This is a bit tricky because one would assume that we would point the feature to a specific folder and all files and folders in that directory would be installed. Unfortunately WiX doesn't do that automatically. We have to declare in the XML files all the files that should be installed. Fortunately there is a way to automate this process.
WiX ships with a couple of command line tools that allow automation of the process of creating an installer. One of those tools is heat.exe. This tool recursively grabs all files in a folder and generates a wxs file listing all the files found. We can then refer to these new files from the feature declaration.
Go to the properties of your project, open the Build section and define a preprocessor variable as such:
Still in the project properties page, open the Build Events section and add some Pre-build events:
"C:\Program Files (x86)\WiX Toolset v3.8\bin\heat.exe" dir "$(SolutionDir)WixSampleInstaller\SoftwareToInstall\SubFolder1" -dr SubFolder1Dir -cg SubFolder1Components -gg -g1 -sf -srd -var "var.SubFolder1SourceDir" -out "$(SolutionDir)WixSampleInstaller\SubFolder1.wxs" "C:\Program Files (x86)\WiX Toolset v3.8\bin\heat.exe" dir "$(SolutionDir)WixSampleInstaller\SoftwareToInstall\SubFolder2" -dr SubFolder2Dir -cg SubFolder2Components -gg -g1 -sf -srd -var "var.SubFolder2SourceDir" -out "$(SolutionDir)WixSampleInstaller\SubFolder2.wxs"
The combination of the variables with the call to the command line tool heat.exe will generate two wxs files, one per feature. In the command I provided a parameter -cg with value SubFolder1Components and SubFolder2Components. This will make sure the new files contain a ComponentGroup XML tag named SubFolder1Components that matches the reference we made in the feature declaration, more specifically in the ComponentGroupRef.
And, to finish the configuration of the installer, we have to declare where the installer should install the software. For that we need a section such as:
<Fragment> <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder"> <Directory Id="APPLICATIONFOLDER"> <Directory Id="SubFolder1Dir" Name="SubFolder1" /> <Directory Id="SubFolder2Dir" Name="SubFolder2" /> </Directory> </Directory> </Directory> </Fragment>
This specifies the structure of the installation folders. The software is going to be installed under the Program Files folder, in a sub-folder that will have the name as specific in the property ApplicationFolderName defined above. The hierarchy of directories corresponds to the directory hierarchy in the user's file system upon installation.
We are now ready to build the solution. If you do so, the Pre-Build events will be triggered and the two new wxs files will be created in the root of the project. When you build you should get an error such as:
Unresolved reference to symbol 'WixComponentGroup:SubFolder1Components' in section .......
This happens because our Product.wxs contains a reference to the Component Group SubFolder1Components declared in the new wxs files but because these new files are not yet part of the project they cannot be found. To solve the problem add the two new wxs files to the project and build it again.
As a result we should now have a successful build and in the project's bin folder our shiny new msi installer.
Running the installer
If we run our installer we can see the result of what we built.
And if we look at the file system we can see the two features we installed.
Opening the Programs and Features confirms the application was installed.
Building installers is not an easy task but WiX proved to be a tool well suited for the job.
There are other good tools out there but the fact WiX is free, open source and has a large community behind it places WiX has a contender in the installers space.
Nevertheless WiX has a steep learning curve and at times it is hard to find information about what you want to do. Being XML based doesn't really help. It would be nicer to have access to a C# fluent API that would generate the installers.
If you are looking for a tool to generate windows installers, WiX is definitely in the front line.
For the purpose of this post I created an installer based on a template but you can create totally customized installers. You can define the window size, the fields that go in each window, you can create custom C# actions, shortcuts, registry keys, you name it and you can probably do it.
As a note, I do not recommend such a tool for deploying web based applications. When researching for my installer I read about people creating installers for web based applications. In my humble opinion there are better ways to deploy these kind of apps. Tools such as Octopus Deploy are much more suited for deploying web based applications.
The number one resource that never failed on me when I was stuck was the following book: WiX 3.6: A Developer's Guide to Windows Installer
The mailing list is also really helpful.
And, finally, the official documentation.
You can get the complete source code of the installer I built for this post in my GitHub repository. Have fun with it!