Lately I was told at work to create an installer for our new product. Till that moment I used to create exe installers with Inno Setup which has many advantages: it’s easy-to-use, powerfull and (the most important :) ) I used it so many times that creating a new installer is a matter of minutes. But this time I heard a big ‘no-no’ for an exe – it had to be msi.
I created a simple “Setup Project” in Visual Studio, played with it for a while and hoped that will work as expected. Well, it worked, but I was told it needed some customizations (graphics, custom dialogs, launching the installed application after install). After doing some googling I was able to do all those things by editing the setup project and modifying the output MSI with Orca. But because every time I needed to create that installer for the new version I had to do some things (for example in Orca) manually – I really hated this solution. So I used google again and I found a really cool, freeware toolset called WiX which does everything I needed. You just create a XML-like file describing the installer and the toolset creates the MSI for you. Great!


Our goal

We will create here a simple MSI installer for our program (called Sample App). The installer will:

  • allow the user to select the installation directory
  • create a program folder in the Start->Programs menu
  • show license agreement dialog
  • have customized graphics
  • give the user the option to launch our application after installing it
  • install the application for all users

One thing to note – there are two versions of WiX available – v2 and v3. As v3 is still in beta stage, I used v2 at the beginning. But then I tried using the beta and it occured to be simpler to use and so much more powerful that I haven’t returned to v2. So I recommend using the WiX 3 (as I will do in the examples below).

A tiny bit of theory

The are some good WiX tutorials on the Internet that will give you more in-depth knowledge about WiX and MSI than this little article. I recommend reading WiX tutorial for a start. Here I will just show you how to create your first MSI installer with WiX, without worrying about more advanced features and background details.
First some basic concepts:

  • our application is a product. It’s identyfied by two unique GUIDs. Product GUID changes every time you change the version of your app. Upgrade GUID has to remain constant, because it’s used by Windows Installer to determine if the other version was installed before
  • a package contains everything that is needed to install your application; each package has it’s own GUID (it should be changed every time you build MSI)
  • a feature is a single part of your application that user can decide whether to install or not (for example sample files, skins, additional languages, documentation); in our example we won’t give user a choice what should be installed, so we will have only one feature installed by default
  • a component is the small unit of software that can be installed (a file, directory, shortcut, registry entry); each feature consists of components; each component has it’s own unique GUID.

The simplest installer

Here is a very basic installer without user interface. It creates a subfolder in “Program Files” called “Sample App” and an executable there.


<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
 <Product Name='Sample App' Id='PRODUCT-GUID-HERE'
  UpgradeCode='UPGRADE-GUID-HERE'
  Language='1033' Version='YOUR-APP-VERSION-HERE' Manufacturer='YOU!'>

  <Package Id='*' InstallerVersion='200' Compressed='yes' />

  <Media Id='1' Cabinet='SampleApp.cab' EmbedCab='yes' />

  <Directory Id='TARGETDIR' Name='SourceDir'>
   <Directory Id='ProgramFilesFolder'>
    <Directory Id='INSTALLDIR' Name='Sample App'>
     <Component Id='MainExecutable' Guid='COMPONENT-GUID-HERE'>
      <File Id='SampleAppEXE' Name='SampleApp.exe' Source='..\SampleApp\bin\Release\SampleApp.exe' Vital='yes' />
      <RemoveFolder Id="INSTALLDIR" On="uninstall" />
     </Component>
    </Directory>
   </Directory>
  </Directory>

  <Feature Id='Complete' Level="1">
   <ComponentRef Id='MainExecutable' />
  </Feature>

 </Product>
</Wix>

As you can see we defined a hierarchy of directories here, a single component (our executable) and one feature. Notice that we put an asterisk instead of package GUID. This tells WiX to generate new GUID every time the install package is created. To build the installer replace the GUIDs with valid numbers and tell WiX to build MSI:

> candle.exe SampleApp.wxs
> light.exe SampleApp.wixobj

Play with it for a while, check if it installs the executable into the Program Files subdirectory, if you can uninstall the application (and if it’s actually removed).

Application folder in Programs menu

Ok, we installed our product, but the user has to manually navigate to its home directory to launch it. We really should create a folder in Programs menu. Here is how we do that.
First we have to create a subfolder in Program Menu. Add the following Directory structure as a child of TARGETDIR directory tag:


<Directory Id="ProgramMenuFolder">
 <Directory Id="ProgramMenuDir" Name="Sample App">
  <Component Id='ProgramMenuDir' Guid='COMPONENT2-GUID-HERE'>
   <RegistryValue Root='HKCU' Key='SOFTWARE\you\Sample App'
    Type='string' Value='Hello World' KeyPath='yes' />
   <RemoveFolder Id="ProgramMenuDir" On="uninstall" />
  </Component>
 </Directory>
</Directory>

This would create a folder we wanted. We have a new component responsible for creating this directory. Add it to our ‘Complete’ feature. We create also a dummy registry entry. This is required because without it light would complain about ICE38 error. This is a workaround :)
We want our shortcut to have an icon. We can define it this way:


<Icon Id="SampleApp.ico" SourceFile="sample.ico" />

This can be put before Feature tag.
Now we have everything that’s needed to create a shortcut in ‘ProgramMenuDir’ folder. To create it we to put this inside out File tag:


<Shortcut Id="startmenuSampleApp" Directory="ProgramMenuDir"
 Name="Sample App" WorkingDirectory='INSTALLDIR'
 Icon="SampleApp.ico" IconIndex="0" Advertise='yes' />

Adding and customizing the UI

WiX offers some predefined user interfaces. We will use InstallDir, which shows license agreement and allows the user to specify the installation directory. To add this interface place the following two lines (for example after Feature closing tag):


<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
<UIRef Id="WixUI_InstallDir" />

While creating the installer we have to use the extension containing our UI. Use the following command:

> light.exe SampleApp.wixobj -ext WixUIExtension

That was pretty simple, wasn’t it? Now we can customize the interface a little bit. First we can provide the our own license. It should be in RTF format. I assume the file is called OurLicense.rtf. You can also change the graphics (I really don’t think the default red skin looks good). To do so create two bitmaps. The first is for the welcome dialog. It’s size is 493×312 pixels, call the file dlgbmp.bmp. The second image is the top banner visible on the rest of the dialogs. Its size is 493×58 pixels. Put it in bannrbmp.bmp file. Both bitmaps should be RLE compressed, cos this will reduce our output msi file size.
Now, that we have the necessary file prepared, we can customize the installer. Those three lines take care of it:


<WixVariable Id="WixUIBannerBmp" Value="bannrbmp.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="dlgbmp.bmp" />
<WixVariable Id="WixUILicenseRtf" Value="OurLicense.rtf" />

You can put those 3 lines after UIRef tag. And voila, we customized our installer!

Launching the application when setup ends

Suppose we want to add a checkbox on the last installer dialog, something like “Launch Sample App when setup exits.” This is more tricky ‘cos we have to copy the default exit dialog and modify it. I’ve found a very useful blog entry describing the way to do it (the second method in “Conditionally launching the application after installation”). Follow the steps described in that article to add the checkbox we wanted. This works almost perfect. However I advise to improve it a little bit.
First we would like the checkbox to be checked by default. To achieve this add the following property to our installer:


<Property Id="LAUNCHAPPONEXIT" Value="1" />

The text next to the checkbox has a fixed application name. Replace ‘Sample App 1.0′ with ‘[ProductName]‘ in MyExitDialog.wxs. The WiX will put our application name automatically.
The third annoying thing is that the ckeckbox is visible every time the installer finishes. If you run the installer for the second time and chose ‘Remove’ option, your application will be removed and the checkbox ‘Launch…’ won’t make any sense – it should be hidden. So let’s hide it. Open MyExitDialog.wxs again and add the condition that will hide the checkbox:


<Control Id="LaunchCheckBox" Type="CheckBox"
 X="10" Y="243" Width="170" Height="17"
 Property="LAUNCHAPPONEXIT" CheckBoxValue="1"
 Text="Launch [ProductName] when setup exits.">
  <Condition Action="hide">
   <![CDATA[WixUI_InstallMode = "Remove"]]>
  </Condition>
</Control>

To make sure the action will not be executed after removing the application, edit the SampleApp.wxs adding the new launch condition:


<Publish Dialog="MyExitDialog" Control="Finish" Order="1"
 Event="DoAction" Value="LaunchApplication">
 LAUNCHAPPONEXIT AND NOT (WixUI_InstallMode = "Remove")
</Publish>

This does the trick. To build the installer execute the following commands:

> candle.exe SampleApp.wxs MyExitDialog.wxs MyWixUI_InstallDir.wxs
> light SampleApp.wixobj MyExitDialog.wixobj MyWixUI_InstallDir.wixobj -out SampleApp.msi -ext WixUIExtension

Minor installer improvements

We can add some minor features to our installer:

  • installing the application for all users

    If you want to install your application for all users, not only the current one, this does the trick:

    
    <Property Id="ALLUSERS">1</Property>
    
  • prerequisites

    Suppose your application requires Microsoft .NET Framework 2.0 to run. Add the following condition to check if it’s installed:

    
    <Condition Message=
     'This setup requires the .NET Framework 2.0 or higher.'>
     <![CDATA[MsiNetAssemblySupport >= "2.0.50727"]]>
    </Condition>
    
  • installer privileges

    If your installed needs administrative right to install the software, add this condition:

    
    <Condition Message=
     'You need to be an administrator to install this product.'>
     Privileged
    </Condition>
    
  • product version

    Right now the product version is specified in SampleApp.wxs file and has to be changed manually when your product version changes. I use a little Python script that reads the version from compiled executable and modifies wxs file. Moreover if the product version changed from the last time, it generates a new product GUID. If you wold like to write that script, here are the two functions that will be useful:

    
    import win32api
    import msilib
    import os.path
    
    def get_version_number(file):
     if os.path.exists(file):
      info = win32api.GetFileVersionInfo(file, "\\")
      ms = info['FileVersionMS']
      ls = info['FileVersionLS']
      return win32api.HIWORD(ms), win32api.LOWORD(ms),
       win32api.HIWORD(ls), win32api.LOWORD(ls)
    
    def generate_new _guid():
     return msilib.gen_uuid()[1:-1]
    

Our final installer

Here is the final version of the SampleApp.wxs file (remember that you need also MyExitDialog.wxs and MyWixUI_InstallDir.wxs as described here).


<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
 <Product Name='Sample App' Id='PRODUCT-GUID-HERE'
  UpgradeCode='UPGRADE-GUID-HERE'
  Language='1033' Version='YOUR-APP-VERSION-HERE' Manufacturer='YOU!'>

  <Package Id='*' InstallerVersion='200' Compressed='yes' />

  <Condition Message=
"You need to be an administrator to install this product.">
   Privileged
  </Condition>
  <Condition Message=
'This setup requires the .NET Framework 2.0 or higher.'>
   <![CDATA[MsiNetAssemblySupport >= "2.0.50727"]]>
  </Condition>

  <Media Id='1' Cabinet='SampleApp.cab' EmbedCab='yes' />

  <Directory Id='TARGETDIR' Name='SourceDir'>
   <Directory Id='ProgramFilesFolder'>
    <Directory Id='INSTALLDIR' Name='Sample App'>
     <Component Id='MainExecutable' Guid='COMPONENT-GUID-HERE'>
      <File Id='SampleAppEXE' Name='SampleApp.exe'
       Source='..\SampleApp\bin\Release\SampleApp.exe' Vital='yes'>
       <Shortcut Id="startmenuSampleApp" Directory="ProgramMenuDir"
        Name="Sample App" WorkingDirectory='INSTALLDIR'
        Icon="SampleApp.ico" IconIndex="0" Advertise='yes' />
      </File>
      <RemoveFolder Id="INSTALLDIR" On="uninstall" />
     </Component>
    </Directory>
   </Directory>

   <Directory Id="ProgramMenuFolder">
    <Directory Id="ProgramMenuDir" Name="Sample App">
     <Component Id='ProgramMenuDir' Guid='COMPONENT2-GUID-HERE'>
      <RegistryValue Root='HKCU' Key='SOFTWARE\you\Sample App'
       Type='string' Value='Hello World' KeyPath='yes' />
      <RemoveFolder Id="ProgramMenuDir" On="uninstall" />
     </Component>
    </Directory>
   </Directory>
  </Directory>

  <Icon Id="SampleApp.ico" SourceFile="eye.ico"/>

  <Feature Id='Complete' Level="1">
   <ComponentRef Id='MainExecutable' />
   <ComponentRef Id='ProgramMenuDir' />
  </Feature>

  <WixVariable Id="WixUIBannerBmp" Value="bannrbmp.bmp" />
  <WixVariable Id="WixUIDialogBmp" Value="dlgbmp.bmp" />
  <WixVariable Id="WixUILicenseRtf" Value="OurLicense.rtf" />

  <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
  <UIRef Id="MyWixUI_InstallDir" />

  <CustomAction Id="LaunchApplication"
   FileKey="SampleAppEXE" ExeCommand="" Execute="immediate"
   Impersonate="yes" Return="asyncNoWait" />
  <UI>
   <Publish Dialog="MyExitDialog" Control="Finish"
    Order="1" Event="DoAction" Value="LaunchApplication">
    LAUNCHAPPONEXIT AND NOT (WixUI_InstallMode = "Remove")
   </Publish>
  </UI>

  <!-- This will ensure that the LaunchConditions
  are executed only after searching -->
  <InstallUISequence>
   <LaunchConditions After='AppSearch' />
  </InstallUISequence>
  <InstallExecuteSequence>
   <LaunchConditions After='AppSearch' />
  </InstallExecuteSequence>

 </Product>
</Wix>

mech