Sunday, July 11, 2010

Automating the creation of your installation packages

Windows Installer XML (WiX) is a great technology for creating installation packages for your .NET based applications. The tools from the WiX toolset do a great job if you have to create frequent releases of your application. Tools like heat.exe help you to create Wix fragments but you have to maintain those files manually by hand. If you practice continuous deployment this is not an option.

With Visual Studio and some small chuncks of msbuild it's still possible to automate the creation of your installation package. The solution to automate your setup is based on the following tools:
  • Wix Setup project for Visual Studio 2010 (part of Wix 3.5)
  • HeatDirectory MsBuild task (part of Wix 3.5)
  • for web applications: Visual Studio Web Deployment 2010
I'm showing you how you do this with Visual Studio 2010 but it's also possible with Visual Studio 2008 (with the use of Visual Studio Web Deployment Projects). Even if the solution is based on Visual Studio there is no need to install Visual Studio on your build server.

First you have the add a new setup project to your Visual Studio solution. You could generate wix fragment files for every component with heat.exe and include them in your setup project. But that's not what we want here. Our goal is that the fragment files will be generated on every build. If we add new output files to a project in our solution, these files will then automaticly show up in our setup.

Fortunatly Wix comes with an msbuild task called HeatDirectory that helps us in accomplishing our goal. We can hook into the build process of the setup project and call this task for every output directory of our solution in the before build target. As an example if your solution contains a windows service project in a folder WindowsService you had to modify the setup project file as follows:

 <Target Name="BeforeBuild">
<ItemGroup>
<CrtHeatDir Include="..\WindowsService\bin\$(Configuration)" />
</ItemGroup>
<HeatDirectory OutputFile="$(ProjectDir)\ProductCrtDirRef.wxs" Directory="@(CrtHeatDir)" ComponentGroupName="CrtComponentGroup" DirectoryRefId="INSTALLDIR_CRT" AutogenerateGuids="true" PreprocessorVariable="var.CrtDirPath" SuppressRegistry="true" SuppressRootDirectory="true" ToolPath="$(WixToolPath)" NoLogo="true" />
</Target>


Notice that we pass a preprocessor variable CrtDirPath to the HeatDirectory task. This serves as the source path of the wix component files in our generated fragment file. We have to define this as a constant in our setup project file:

 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>Release;CtDirPath=$(SolutionDir)WinodwsService\bin\$(Configuration)</DefineConstants>
</PropertyGroup>


Creating output files for Web Application Projects

In our example we could create our component files based on the output directory of our windows service project. If you have a web application in your solution you have to make a little detour as there is no such output directory. With the help of Visual Studio 2010 Web Deployment we can create the output of your web project and use is it as input for heat. If you right click on a web application project in Visual Studio 2010 there is a command called "Build Deployment Package". This is the command that we want to integrate in our setup project. The msbuild target behind this command is called Package. We can call this target of the web application project within our setup project with a call to the task msbuild:

<Target Name="BeforeBuild">
<ItemGroup>
<WebProjects Include="..\WebServices\WebServices.csproj" />
<SwsHeatDir Include="..\WebServices\obj\$(Configuration)\Package\PackageTmp" />
</ItemGroup>
<Msbuild Projects="@(WebProjects)" Targets="Package" Properties="Configuration=$(Configuration);Platform=AnyCPU">
</Msbuild>
<HeatDirectory OutputFile="$(ProjectDir)\ProductSwsDirRef.wxs" Directory="@(SwsHeatDir)" ComponentGroupName="SwsComponentGroup" DirectoryRefId="INSTALLDIR_SWS" AutogenerateGuids="true" PreprocessorVariable="var.SwsDirPath" SuppressRegistry="true" SuppressRootDirectory="true" ToolPath="$(WixToolPath)" NoLogo="true" />
</Target>


Package creates it's output in the web application's obj directory. The directory PackageTemp contains all the files that we have to include in our deployment.

The next step in automating our setup would be to include the build of our setup in a continuous integration or nightly build. During this build it is often practical to include the product version in the name of the msi and to copy it to a drop location where it can be downloaded by testers or even customers.

Even if WiX rather supports a manual creation strategy of deployment packages the toolset provides tools for automation. Thanks to MsBuild and Visual Studio you only have to do a little bit of additional work to automate the creation of your installation package.

Download sample source