Deployment. I bet it’s your favourite part of development right? It’s not? Well it should be, it’s the moment that wonderfully crafted piece of genius you spent the last 6 months slaving over finally gets released into the wild. Cue rapturous applause from the crowd of adoring fans clustered around your desk and a fat bonus from the boss. But unfortunately it never seems to go that way; instead you pass the built code to the server guys along with a list of instructions which spell out step by step exactly what needs to be done. And they don’t follow it. You hit the homepage only to be greeted by a red screen of death. The crowd wanders off looking for a new hero and the boss gets himself a nice new solid gold mug.

It doesn’t have to be like that though; with a little bit of work you can take complete control of your deployment process and make the server boys happy at the same time. The first port of call for most people is the MsDeploy stuff, but there are a few reasons against:

  • It won’t work with IIS 6 (yes, I know there are workarounds, but they require local admin permissions on the entire box – not feasible for production);
  • Its only for web applications – so if you want a service installing you’re going to have to mess about with batch files or learn about installers anyway;
  • It’s not extensible to any great degree.

Enter WiX – the windows installer XML tool. This is the tool that the guys at Microsoft used to create the office installer, as well as loads of other stuff. It also has a reputation for a stiff learning curve. The bad news is that, yes, the learning curve is a bit steep, but the good news is that it’s also not too high. A couple of hours of head scratching and you’ll save yourself countless hours of fretting later. In this series of posts I’ll take you from the most basic installer possible to a full featured product allowing for automatic IIS registration, file permission settings and installation and startup of your services.

Stop! Hopefully by now I’ve convinced you that WiX will up your pay, make you famous, and get rid of that unfortunate rash. If so, you need to go install the WiX toolset from codeplex. It’s OK, we’ll wait.

The web application

All done? Good, but if we’re going to create an installer, we better have something to install. For this I’ve created the imaginatively titled WixInstallerExampleWeb in Visual Studio, which is a really simple web app containing a single aspx page, a web.config (plus transformations) and a txt file. (You can get the code for all the examples in this post from here if you don’t want to do it yourself.) Our aspx page displays a title and message:

<h1 id="MainHeader" runat="server">WiX installer example app</h1>
    
Welcome to the site! We've logged your visit, thanks for stopping by.

While it's code behind adds a custom bit of configuration text to the title, and logs the visit to the filesystem:

protected void Page_Load(object sender, EventArgs e)
{
MainHeader.InnerText += " - " + ConfigurationManager.AppSettings["InstallationType"];

TextWriter tWriter = new StreamWriter(Server.MapPath("~/DataFiles/UserLogs.txt"));
tWriter.Write("Accessed at " + DateTime.Now.ToString("dd MMM yyyy - hh:mm:ss"));
tWriter.Close();
}

Not exactly rocket science, but enough for our needs so far, and it poses a few deployment challenges:

  • it will need registering with IIS;
  • we will need some permissions on the file system;
  • different build types may need corresponding values in the InstallationType application setting.

We’ll get to that later though. For now let’s just get the installer copying the right files for us.

The installer

First up you need to add a new installation project to your solution. When you add a new Visual Studio project you should now see a new category called Windows Installer XML. You want the standard Setup Project and could call it something flashy like WixInstallerExampleWebInstaller:

 

A Note On GUIDs

In the following examples there will be lots of GUIDs used. It is essential that you replace these with GUIDs you've generated yourself. If you don't things will go seriously wrong, up to and including a dragon crashing through the office window, eating your sandwich and setting fire to the carpet. You have been warned. A nice macro for generating GUIDs right inside visual studio can be found here. Anyway...

Product.wxs

In the new project you’ll see a single file named Product.wxs. This is the main part of your installer; all the other files are just helping out. The main Product node is the installer; there is always exactly one of these: fill in the name, manufacturer and version to match your project. Ignore the Package and Media nodes, they will always stay like this until you start doing really complex stuff. This leaves us with 2 groups of nodes: Directory and Feature.

The Directory Section

The directory section defines the structure of what we'll be installing and where it goes. So we build up our default location using the Directory nodes, and in it place Components (i.e. the application) to install. Edit your Directory section so that it looks like this:

<Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="WEBROOT" Name="WebRoot">
    <Directory Id="INSTALLLOCATION" Name="WixInstallerExampleWeb">
       <Component Id="ProductComponent" Guid="63c72a76-d3a8-4da2-849f-c83527b40690">
         <File Id="Default" Source="../WixInstallerExampleWeb/Default.aspx" />
       </Component>
    </Directory>
  </Directory>
</Directory>

This will cause the Default.aspx page to be installed to C:\WebRoot\WixInstallerExampleWeb\ (don't worry, we'll see how to make this configurable later).

You can have as many components here as you want here and they don't just define files, they can also define registry settings, IIS settings, service startup instructions - they are the distinct chunks of your application. For now though we're happy with just the one for our site content.

The Feature Section

While the Directory nodes define where things will go, the Feature section defines which components we actually want to install. You can add 100 components into INSTALLDIRECTORY and nothing will be installed until you reference them in the feature section. That's easily done though - change the Feature node to look like this:

<Feature Id="ProductFeature" Title="Wix Installer Example Web" Level="1">
   <ComponentRef Id="ProductComponent" />
</Feature>

It's not really too complex - you reference a component to install and specify the ID of the component you created in the Directory section.

So that's it, you've created an installer. Learning curve, what learning curve? Give it a build and an MSI file should pop out into the bin/Debug folder. You can go ahead and run it now if you like, and you should see something like this flash up then disappear:

 

Go take a look at C:\WebRoot\WixInstallerExampleWeb. You should see your Default.aspx page sat there. Score 1! Next, go look in your add/remove programs list. Right down at the bottom you should see this:

 

Cool huh?

What we've created here is nice, but it's not exactly functional. For a start we're only installing the Default.aspx page, without the config file or the required DLL, we also haven't set up any IIS settings. We can't even specify an install directory! We'll deal with them one at a time, starting with getting the right files installed. First though, click on the entry in add/remove programs and push the Remove button, which will wipe the app off your computer for you. Next up - Heat.

Heat.exe

The smart people who designed all this had a bit of a quirky moment when they named the exe files which do all the work when you build your installer, everything has a fire reference. For instance, the compiler itself is called candle.exe, there are plenty of others though, the most commonly used of which is heat.exe. What heat will do is inspect a location on disk and output a file containing Directory and Component nodes, which saves you from having to keep your Product.wxs file in line with your application manually. What's even better is that they also included a nifty MsBuild wrapper for it, so we can easily include it right in our build sequence!

If you've never used MsBuild before then don't worry - it's not that hard!

In solution explorer, right click on your installer project and select 'Unload Project'. This will grey out its entry and you can now right click again and select 'Edit WixInstallerExampleWebInstaller.wixproj' which will pop up an XML file representing your project. For those of you familiar with MsBuild, notice that a WixTargetsPath has been pulled in for you in the top PropertyGroup, and Imported at the bottom. Everyone else, all you need to know is that the WiX functionality has been imported for you automatically.

Near the bottom of the file there is a commented out section containing a Target called 'BeforeBuild'. This section funnily enough defines a list of things to do before the project is built. What we want to do is generate a Directory section using heat.exe before the build happens so that it is available to the compiler during the actual build. Uncomment the section and modify it to look like this:

<Target Name="BeforeBuild">
  <PropertyGroup>
    <LinkerBaseInputPaths>..\WixInstallerExampleWeb\</LinkerBaseInputPaths>
  </PropertyGroup>
  <HeatDirectory OutputFile="WixInstallerExampleWeb.wxs"
    Directory="..\WixInstallerExampleWeb\"
    DirectoryRefId="INSTALLLOCATION"
    ComponentGroupName="WixInstallerExampleWeb_Project"
    SuppressCom="true" SuppressFragments="true" SuppressRegistry="true"
    SuppressRootDirectory="true" AutoGenerateGuids="false" GenerateGuidsNow="true"
    ToolPath="$(WixToolPath)" />
</Target>

So, we're running heat on the directory "..\WixInstallerExampleWeb\" and outputting the details to WixInstallerExampleWeb.wxs. There are a few Suppress attributes which can safely be ignored for now - I'm sure you can figure them out fairly easily. The important bits are the DirectoryRefId and ComponentGroupName attributes. DirectoryRefId provides a link to the INSTALLLOCATION you put in the Product.wxs file earlier, while the ComponentGroupName is a new ID which you'll use later.

LinkerBaseInputPaths

This awkwardly named little node does a bit of magic, it brings in the files from the referenced folder so that they can be seen by the compiler - notice that so far we haven't even put a reference in to the web project, it's all done via directory references.

We're done with the pre-build for now, so close the file and reload the project. Go on and build it again now and select the 'Show all files' option at the top of the solution explorer. You should see a WixInstallerExampleWeb.wxs file - include it in the project.

Take a look at the file and you'll see it's a collection of Directory, Component and File nodes just as if you'd created the structure yourself in Product.wxs. There are a couple of differences though:

DirectoryRef

This node references the INSTALLLOCATION folder you specified in Product.wxi and the HeatDirectory command. It allows all these nodes to be specified in this external wxs file.

ComponentGroup

This is simply a collection of component references - it means that in the next step we don't need to reference all the generated components separately. Notice that it's ID is the value you specified in the HeatDirectory command.

Bringing It All Together

Right, let's finish this off so we can all get home. In your Product.wxs file, remove the Component and File nodes from the INSTALLLOCATION Directory, and remove the ComponentRef from Feature. Then add a new ComponentGroupRef into Feature with an Id matching the one generated for the ComponentGroup in the generated wxs file. You should end up with something like this:

<Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="WEBROOT" Name="WebRoot">
    <Directory Id="INSTALLLOCATION" Name="WixInstallerExampleWeb" />
  </Directory>
</Directory>

<Feature Id="ProductFeature" Title="Wix Installer Example Web" Level="1">
  <ComponentGroupRef Id="WixInstallerExampleWeb_Project" />
</Feature>

Build your project and run the resulting installer.

It's Good, But...

So, now our installer is installing all the files from your project to the output folder. This is good, and it will run if you set it up as a virtual directory, but... we still have issues - we're copying all the non-needed files, such as the csproj file and cs files. We've also got Web.Debug.config and Web.Release.config in there - we really want to have the transformations run for us as part of the compile.

That's it for now, in part 2 I'll show you how to install the properly packed project output instead of the entire project source, and after that we'll look at IIS, a user interface and some of the advanced things you can do with MsBuild and Heat.exe. If you want to be notified when the next instalment is available you can subscribe to my RSS feed here.

Source Code

Here is the full source for this example: WixInstallerExample-01.zip (164.17 kb)