Last time, I showed you how to create a basic installer based on an even more basic web application. If you didn't read that one go take a look now; it's right here. This time we're going to dig a little deeper and show you how to install the actual project output, rather than the full source code. In reality this post doesn't contain much WiX functionality; it's mostly MsBuild code. Strap yourself in - we're diving into the build system!

I should state right now that the method of grabbing project output described here I first learnt from the excellent Paraesthesia blog. This guy writes some cool posts and you should check it out. I've made a few changes to his code to fix some issues I had, and to make things easier when we get onto the later articles.

MsBuild - The 30 Second Overview

MsBuild is one of those topics that people seem to find quite scary, but in reality it's fairly easy to do pretty cool stuff with. There are 2 concepts to get your head round.

The Project File

All your project (.csproj) file is, is an MsBuild script, which gets maintained for you by Visual Studio in the background. Anything that the compiler does to your project - building, packaging, different configurations, whatever - goes through that script.

Targets

MsBuild scripts are run via targets. This is simply an entry point - think of it as a static class - each target is one of its methods. In exactly the same way that these methods can call each other, targets can (and will!) call each other. Targets are things like 'Build' or 'Package'. You can define your own too, although we won't be covering that here.

Now, on with the fun!

The Problem

At the end of the last post, we had an installer which copied all the code for our application to a folder, including all the cs files and our untransformed web configs:

Ideally what we'd prefer is to only install the files required to run the application. We could do this manually, but a much cleaner solution is to get the build process to package the application and then use that as the location to run Heat on. Next stop - using MsBuild to package the app.

Packaging applications via MsBuild

To do this you'll need to give your installer application a reference to your web application. When this is done unload the installer application and edit the wixproj file. Toward the bottom you should see a ProjectReference node which points to your web's csproj file. You're going to add a new node to this called PackageThisProject with a value of true:

<ProjectReference Include="..\WixInstallerExampleWeb\WixInstallerExampleWeb.csproj">
  <Name>WixInstallerExampleWeb</Name>
  <Project>{d23a374d-764c-40ba-b566-4d7c55319236}</Project>
  <Private>True</Private>
  <DoNotHarvest>True</DoNotHarvest>
  <RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
  <RefTargetDir>INSTALLLOCATION</RefTargetDir>
  <PackageThisProject>True</PackageThisProject>
</ProjectReference>

Next, in the BeforeBuild target, replace the PropertyGroup and HeatDirectory nodes you had before with the following:

<MSBuild Projects="%(ProjectReference.FullPath)" Targets="Package"
  Properties="Configuration=$(Configuration);Platform=AnyCPU"
  Condition="'%(ProjectReference.PackageThisProject)'=='True'" />
    
<Copy SourceFiles="%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\TransformWebConfig\transformed\web.config"
  OverwriteReadOnlyFiles="true"
  DestinationFolder="%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\Package\PackageTmp\"
  Condition="'%(ProjectReference.PackageThisProject)'=='True'" />

<PropertyGroup>
  <LinkerBaseInputPaths>%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\Package\PackageTmp\</LinkerBaseInputPaths>
</PropertyGroup>

<HeatDirectory OutputFile="%(ProjectReference.Filename).wxs"
  Directory="%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\Package\PackageTmp\"
  DirectoryRefId="INSTALLLOCATION"
  ComponentGroupName="%(ProjectReference.Filename)_Project"
  SuppressCom="true" SuppressFragments="true" SuppressRegistry="true" SuppressRootDirectory="true"
  AutoGenerateGuids="false" GenerateGuidsNow="true" ToolPath="$(WixToolPath)"
  Condition="'%(ProjectReference.PackageThisProject)'=='True'" />

This isn't really as complicated as it looks. Firstly let's look at the various variables being used here:

  • %(ProjectReference.RootDir)%(ProjectReference.Directory) will be substituted for the path to your web project;
  • $(Configuration) will be substituted for the current build configuration (debug or release);
  • %(ProjectReference.FileName) will be substituted for the name of your web project;
  • %(ProjectReference.PackageThisProject) is the new node we added to the project reference.

Now onto the logic: for each project reference (in this example we are just using the one but it will happily scale to as many as you like) we:

  • Make a call to MsBuild specifying the Package target. This will cause a properly packaged output be placed in obj\Debug\Package\PackageTmp under the web project;
  • Copy a transformed web.config file from obj\Debug\TransformWebConfig\transformed\web.config into the packaged web project. I found this to be required in certain cases to stop template values being installed;
  • Set LinkerBaseInputPaths and run HeatDirectory exactly as before except now we target the packaged output instead of the project source.

Finally, each of the nodes also has a Condition attribute. This checks the PackageThisProject value we set earlier, and ensures that this logic is not applied to any other references we add to the project.

Finishing Up

We're all done, so reload the installer project and build. If you then look at your web project's folder in windows explorer you should see an obj\Debug folder, containing a tree of files mapping to what we've done above:

 

If you now run your newly built installer it should be this content which gets installed - easy huh? That's it for now, but in part 3 we'll start looking at a user interface for customising the install location and displaying a licence agreement, which should lead us on nicely to IIS integration and custom WiX UI. If you want to be notified when the next instalment is available you can find my RSS feed here.

Source Code

Here is the full source code for this example: WixInstallerExample-02.zip (617.77 kb)