Automate Releases With MSBuild And Windows Installer XML
Sayed Ibrahim Hashimi
This article discusses:
|This article uses the following technologies:
Visual Studio, Windows Installer XML (WiX), MSBuild
Code download available at: WiX 2007_03.exe(498 KB)
Introduction to WiX
Creating WiX Files
WiX and MSBuild
Automating Builds and Packaging
Anatomy of the MSBuild Script
Putting It All Together
Customizing the Process
During the course of development, it's important to have an automated build process. Equally important is having an automated means of creating releases. Unfortunately, in many organizations-especially smaller ones-this doesn't happen. Typically, you'll find that the release is simply cobbled together at the last minute. However, if you take the time to set up an automated build and release plan, you'll save countless hours that could be better spent on tasks other than building and releasing your project.
In this article, I'll show you how to achieve an automated and repeatable build and release process in your organization using the Microsoft® Build Engine (MSBuild) and the Windows® Installer XML (WiX) toolset. This article covers WiX v2 (note that when WiX v3 is released, some of the syntax examples won't translate directly). The techniques described here will be relevant whether or not you are using WiX to create your releases, though WiX does simplify the process. They can also be applied, with some modifications, to applications that are being developed without using the Microsoft .NET Framework 2.0.
I'll assume you're familiar with MSBuild (if you need to refresh, look at my June 2006 MSDN®Magazine article "Inside MSBuild: Compile Apps Your Way with Custom Tasks for the Microsoft Build Engine"). I will provide an overview of the WiX toolset for those who are not familiar with it. See the "MSBuild and WiX Resources" sidebar for references to more related articles and tools. In this article I'll use my Sedodream MSBuild project for demonstration purposes. You can get the latest sources at www.codeplex.com/Sedodream.
Introduction to WiX
When creating an application, the end result is usually to install and run it on production machines. The WiX toolset can help you do this. In this section, I will describe WiX and give an introduction to creating installers using WiX.
WiX describes what the installation looks like on the target machine. Windows Installer is used in many Microsoft applications, from Visual Studio® to Microsoft Office. You might be surprised to learn that WiX is actually an open source project and is hosted at sourceforge.net. You can download the latest binaries and sources at wix.sourceforge.net. When you download and install WiX, you'll find a number of executables installed as summarized in Figure 1. I will focus on using the Candle.exe and Light.exe tools.
Figure 1 WiX Components
|Candle.exe||Transforms the WiX source file into an intermediate representation. This is actually another XML file, but you should never manually change those generated files.|
|Dark.exe||Converts an MSI file into an appropriate WiX source file-can be thought of as "decompiling" the installer.|
|Light.exe||Generates the Windows installer from the intermediate representation of the WiX source file(s).|
|Lit.exe||Generates WiX libraries that can be used to build other installer packages.|
|Tallow.exe||Used to create the WiX source XML to replicate its files and folders in an installation directory or file.|
|WixCop.exe||Checks a WiX source file for potential problem areas, similar to FxCop.|
WiX uses a declarative language, not a procedural one, which means you describe what your installation will look like, not what steps need to be followed in order to achieve it. This may be different from what you are accustomed to, but it is surprisingly easy to learn. Typically, your WiX source files will be populated by describing files that will be installed on the target machine. I will focus on those components here.
In a WiX source file, there are three main elements with respect to the files you intend to install: File, Component, and Feature. A File element is a reference to an individual file. Files must be contained in a Component element, which is the smallest unit to be installed. That is, if you have a component containing 100 files and you install that component, all of its contained files are installed. Conversely, if that component is not installed, none of the files are installed. Creating components that contain a large number of files is not recommended.
Components are always contained in a Feature element and can be contained in more than one feature. A feature is a set of components and potentially of sub-features. If your installer has a graphical interface that allows the user to select which items to install, they are actually selecting features.
Creating WiX Files
You can author WiX source files in any text or XML editor you choose. You can also use Visual Studio to author WiX source files with IntelliSense®. If IntelliSense is not enabled when you are editing a WiX source file using Visual Studio, all you have to do is copy the wix.xsd file into the Visual Studio schemas directory. Typically, this directory resides at %Program Files%\Microsoft Visual Studio 8\Xml\Schemas. As you create WiX source files you will also need to generate many GUIDs. Visual Studio has a tool that you can use for this purpose, and there are Visual Studio macros available so you can assign a shortcut to directly insert a new GUID as well.
Now let's start by creating a new WiX source file. The first element is always going to be a Wix element. Child elements for the Wix element include Product, Fragment, Module, and PatchCreation. The type of desired output will drive which of these you will use. In this case, I want the end result to be an installer database (MSI) for my project so I'll use the Product element. The beginning of the WiX v2 source file, Sedodream.wxs, for the sample project is shown here:
<?xml version='1.0' encoding='UTF-8'?> <Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'> <Product Name='Sedodream MSBuild Project' Id='C9D25926-FCE0-4EB6-8FF5-4686EE5AB089' Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='SedoTech' UpgradeCode='9C5E4073-EFDE-419B-935D-CE2632BC560E'> <Package Id='????????-????-????-????-????????????' Keywords='Installer' Description='Sedodream MSBuild Project Installer' InstallerVersion='100' Languages='1031' Compressed='yes' SummaryCodepage='1252' /> ...
As you can see ,the Product element has many attributes, the most important of which are Id and Name. The Id is used to uniquely identify the product. You must change this for a new major release. You should also make a note of its value for later reference. The value of the Name attribute is what will be displayed in the Add/Remove programs panel, and if your installer has a UI it will be shown on the introduction page.
The first child of the Product element, the Package element, is shown here as well. Notice that the Id attribute contains a series of ? characters in the format of a GUID. By using this syntax, the code specifies that the Id should be generated at build time. When you are creating installers, you want the Package Id to be different for each installer created, even from one build to the next. This is the only GUID you can have auto-generated; all other GUIDs need to stay the same and should be recorded for future reference.
Almost every installer will place at least one file onto the target machine, and this installer is no different. Since WiX uses a declarative approach, I'll declare what the directory structure is and that will be recreated on the target machine. I do this using a series of Directory elements:
<Media Id='1' Cabinet='Sedodream.cab' EmbedCab='yes'/> <Directory Id='TARGETDIR' Name='SourceDir'> <Directory Id='ProgramFilesFolder' Name='PFiles'> <Directory Id='MSBuildDir' Name='MSBuild'> <Directory Id='INSTALLDIR' Name='Sedodrea' LongName='Sedodream'>
The Media element in this sample specifies that a cabinet file will be generated and placed into the installer database. This is a required element, and if you have installations spanning more than one medium you can have more than one. For example, if you are distributing your application on a CD, but it needs to span over multiple discs. In most cases, you won't have to worry about this issue.
Following Media is the series of Directory elements. The first Directory element will be a virtual element that simply encapsulates the other entries. The entry under the TARGETDIR directory element has the Id value of ProgramFilesFolder. As you may have guessed, this is a well-known location and Windows Installer will set its value when launched. There are other system folders you can use; a complete list is available from msdn.microsoft.com/library/en-us/msi/setup/system_folder_properties.asp. These system properties always resolve to their full paths.
The next elements provide names of custom directories. The directory will be named using the LongName attribute if possible; otherwise the Name attribute is used. If those directories don't exist, Windows Installer will create the appropriate one at install time. The complete tree of what is declared in the previous code fragment ends up being C:\Program Files\MSBuild\Sedodream.
After you declare your directory structure, it's time to start creating Component declarations. As noted earlier, the smallest unit that can be installed is the component, which can consist of many different items including files, shortcuts, registry keys, and certificates. Design your components so they are independent of each other. That is, when you install or uninstall a component there should be no adverse effect on other components. Inherently this means that items should only be contained in a single component. If that's not the case, you may want to reconsider how your components are organized. It's not unusual for a component to contain only a single file.
Each Component declaration must have an Id and a GUID attribute. The Id is the name that you use to refer to it, and the GUID is a unique identifier for Windows Installer to use. It is very important to ensure that none of your component GUIDs repeat.
Figure 2 shows a component from the sample WiX source file. You can see that it contains four File elements and two XmlFile elements. The File element is simply a reference to a file that needs to be placed on the target device. The File element can have many different attributes, but the ones you'll mostly use are Id, Name, LongName, Source, and DiskId, described in Figure 3.
Figure 3 File Attributes
|Id||An identifier that can be used to refer to the file.|
|Name||The short name of the file in 8.3 format.|
|LongName||The long name of the file; this is the name that will be used on the target machine if it supports long file names. Otherwise the Name value will be used.|
|Source||The relative path to the file on the machine that is creating the installer database.|
|DiskId||The identifier of the media item that this file will be contained in.|
Figure 2 Defining Components
<Component Id='TaskBinFiles' Guid='38736E2E-BEB7-48A9-A2B3-138A57A69D45'> <File Id='MSBCommonDLL' Name='CommoDLL' LongName='Sedodream.MSBuild.Common.dll' Source='Sedodream.MSBuild.Common.dll' Vital='yes' DiskId='1'/> <File Id='SedodreamLoggersDLL' Name='Logger' LongName='Sedodream.MSBuild.Loggers.dll' Source='Sedodream.MSBuild.Loggers.dll' Vital='yes' DiskId='1'/> <File Id='SedodreamTasksDLL' Name='Tasks' LongName='Sedodream.MSBuild.Tasks.dll' Source='Sedodream.MSBuild.Tasks.dll' Vital='yes' DiskId='1'/> <File Id='SedodreamTASKS' Name='SeTasks' LongName='Sedodream.tasks' Source='Sedodream.tasks' Vital='yes' DiskId='1'/> <!-- Merge in the Xsd for the custom tasks we have created to have IntelliSense --> <XmlFile Id='SedoSche' File='C:\Program Files\Microsoft Visual Studio 8\ Xml\Schemas\1033\Microsoft.Build.xsd' Action='createElement' ElementPath='//xs:schema' Name='xs:include' Permanent='no' Sequence='0'/> <XmlFile Id='SedoSch2' File='C:\Program Files\Microsoft Visual Studio 8\ Xml\Schemas\1033\Microsoft.Build.xsd' Action='setValue' ElementPath='//xs:schema/xs:include[\not(@schemaLocation)[\]]' Name='schemaLocation' Value='MSBuild\Sedodream.MSBuild.xsd' Permanent='no' Sequence='1' /> </Component>
The XmlFile element actually modifies an existing XML file on the target machine. It inserts a new element into the Microsoft.Build.xsd file, which allows IntelliSense to be enabled for the custom MSBuild tasks contained in the sample project.
Features are the items that the user will select for installation if your installer shows a UI. Here are the Feature declarations from the sample project:
<Feature Id='Complete' Title='Sedodrem Core' Description='MSBuild libraries' Display='expand' Level='1' ConfigurableDirectory='INSTALLDIR'> <ComponentRef Id='TaskBinFiles'/> <ComponentRef Id='NUnitFiles'/> <Feature Id='Samples' Title='Samples' Description='Contains samples of Sedodream usage' Display='expand' Level='100' ConfigurableDirectory='INSTALLDIR'> <ComponentRef Id='Samples'/> </Feature> </Feature>
Like many other WiX elements, the Feature element has many possible attributes, some of the more commonly used ones are described in Figure 4.
Figure 4 Feature Attributes
|Id||An identifier that can be used to refer to the feature.|
|ConfigurableDirectory||Allows you to set the installation location from a property, which can be determined by a user with a UI.|
|Description||A description for the feature, which will be displayed to a user with a UI.|
|Level||Specifies the install level for the feature. A value of zero means the feature will not be installed. Non-zero values that are less than or equal to the INSTALLLEVEL mean the feature is installed.|
|Title||A title for the feature whose value will be displayed in the tree of features if a UI is being used.|
In this WiX snippet the main feature, Complete, contains a reference to two components and a sub-feature. The sub-feature references are achieved by a child Feature element. The WiX source file can contain as many of these as necessary. The component reference uses a ComponentRef element. When using the ComponentRef element you have to use the same ID value that is used in the Component element you are trying to reference.
WiX and MSBuild
In the latest releases for WiX v2 you'll find a wix.targets file, which contains definitions that will assist you in creating WiX releases from MSBuild. The WiX team has created a set of MSBuild tasks that are referenced by the wix.targets file. Those tasks are all contained in the WiXTasks.dll assembly and summarized in Figure 5.
Figure 5 MSBuild Tasks for WiX
|Candle||Creates the intermediate representation of the WiX source files by calling the WiX compiler.|
|Lit||Creates a WiX library from the intermediate representation.|
|Light||Creates the final installer from the intermediate representation by calling the WiX linker.|
The way in which you'll build installers is very similar to how managed projects are built now. For example, when creating a C# project, your project file will contain all the necessary properties and items to build the project, but all the steps to build the project are contained in the Microsoft.CSharp.targets file. This file is included into the project file with the MSBuild Import element. For WiX, your project file will define all necessary properties and items and then include the wix.targets file to define the logistics of how to build the final packages. Figure 6 provides a list of the minimum declarations required to successfully build an installer using wix.targets.
Figure 6 Declarations Needed to Build an Installer
|ToolPath||Contains the path to the WiX installation directory. This is where the wix.targets file resides.|
|OutputName||Path and name of the output that will be created. The name should not include any extension because it will be used for both the intermediate representation and the final package file. This property is analogous to the OutputFile argument for Candle.exe and Light.exe.|
|OutputType||Represents what the final file should be. The possible values are package, module, library, and object. This will determine whether the Light or Lit task will be invoked and the extension of the final file created. Since we want to create an installer, we will provide the value package for this property.|
|Compile||MSBuild item that contains the WiX source files to be compiled.|
|BaseInputPath||Serves as the input path for source files. Although technically not required, most times this should be employed. Otherwise you'll have to make sure to invoke msbuild.exe from the correct directory.|
To create the installer you need to create an MSBuild file to define the required properties and items. In this case, I'll name the file SedodreamMSI.wproj. This file is shown in Figure 7.
Figure 7 MSBuild File SedodreamsMSI.wproj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> <!--=============================================================== These must be declared BEFORE the statement that imports the wix.targets file ==================================================================--> <PropertyGroup> <!-- The location pointing where WiX is installed --> <ToolPath>C:\Data\Development\WiX\</ToolPath> <!-- Required Property by WiX --> <OutputName Condition="$(OutputName)==''" > $(Configuration)Sedodream</OutputName> <!-- Required property by WiX --> <OutputType Condition="$(OutputType)==''" >package</OutputType> <!-- Input path to source files --> <BaseInputPath Condition="$(BaseInputPath)==''"> $(PackageRoot)$(Configuration)</BaseInputPath> </PropertyGroup> <ItemGroup> <!-- Required WiX item. Files in this item are sent to the Candle tool. --> <Compile Include="$(BaseInputPath)\Sedodream.wxs"/> </ItemGroup> <Import Project="$(ToolPath)wix.targets"/> </Project>
As you can see, there's not much to this MSBuild file, just a few properties and a lone item defined. The file assumes that a WiX source file, Sedodream.wxs, has been created and is residing in the BaseInputPath directory. The WiX file also makes some assumptions about where files are placed. For now let's assume that all the files are where they need to be.
To build the installer I will need to invoke MSBuild on the project file. I open the Visual Studio 2005 command prompt, navigate to where the file resides, and execute the following command:
The output is shown in the Figure 8.
Since I didn't define a target to execute, the DefaultTargets were executed, and this was defined to be Build in the Sedodream.wproj file. From the output you can see that a file named ReleaseSedodream.msi was created in the bin\Release folder. At this point you can execute the installer to ensure that it works as expected. Before continuing with our main goal, I will cover an advanced MSBuild topic, batching, in the next section. This is necessary because batching is used throughout the process that will build the product and its release.
Figure 8** WiX Installer Creation Output from MSBuild **(Click the image for a larger view)
When using MSBuild you'll discover that there is no looping construct to be found. In place of a loop you can employ batching, which uses item metadata to break items into different categories called batches or buckets that consist of one or more items. Once the items are separated you can iterate through each batch. This is used throughout the build process for managed projects. There are two broad categories of batching: task batching and target batching. In task batching, when you execute a task, the MSBuild engine will determine what buckets need to be created and execute the task over those buckets. To use task batching, you provide an item's metadata to the task.
To demonstrate batching, imagine you are faced with the following scenario. You have to copy a set of files from one location to one or more other locations and you have two options: you can copy the files using individual Copy tasks to each location, or you can use batching to achieve this in one copy element. The first approach, although simple, is not a good solution because it is difficult to maintain. To illustrate the second approach, have a look at this very simple MSBuild file, batching01.proj:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="CopyFiles"> <ItemGroup> <SourceFiles Include="*.txt"/> <Dest Include="One;Two;Three;Four;Five"/> </ItemGroup> <Target Name="CopyFiles"> <Copy SourceFiles ="@(SourceFiles)" DestinationFolder="%(Dest.FullPath)"/> <Message Text="Fullpath: %(Dest.FullPath)"/> </Target> </Project>
In this file, I declare an item SourceFiles to include any file that ends with .txt, and I also declare a Dest item that includes five locations. In the target CopyFiles, I use the Copy task to copy the SourceFiles to each destination. Since I pass the FullPath metadata into the Copy task, the MSBuild engine will create a batch of all the Dest items that have distinct FullPath values, then the Copy task is invoked for each batch. This is task batching. To execute this I can invoke the following command from the directory that contains the batching01.proj file:
Now I'll demonstrate the same result with target batching. For target batching, the batches are created based on the Inputs and Outputs of the target so I have to pass an item's metadata for these values:
<Project xmlns="http://schemas. microsoft.com/developer/msbuild/2003" DefaultTargets="CopyFiles"> <ItemGroup> <SourceFiles Include="*.txt"/> <Dest Include="One;Two;Three;Four; Five"/> </ItemGroup> <!-- These targets demonstrate target batching --> <Target Name="CopyFiles" Inputs="@(SourceFiles)" Outputs="%(Dest.FullPath)"> <Copy SourceFiles="@(SourceFiles)" DestinationFolder= "%(Dest.FullPath)"/> </Target> </Project>
Target batching is driven off of the targets Inputs and Outputs. Since Outputs contains the Dest.FullPath declaration the target will be invoked once per bucket. This is similar to task batching, except that the contents of the entire target will be repeated once per batch, as shown in Figure 9.
Figure 9** MSBuild Output for Target Batching **(Click the image for a larger view)
From this output you can see that the target, CopyFiles, was invoked once per batch for the Dest item. In the example of automating the build/package process, I'll use batching to create an installer for each combination of configuration platform and flavor.
Automating Builds and Packaging
For each organization the steps involved to build and package are going to be slightly different, in some cases due to the technologies used, and in others because of organizational requirements. I have put together a set of steps that I think will serve many organizations well, though it may require some fine tuning based on your particular scenario. The goal of an automated build-and-package process is twofold: to create a repeatable public build process and to create an automated repeatable package process. There are three core steps: get latest, build, and package, but this is broken down into many smaller steps as shown in Figure 10.
Figure 10** Build Process **(Click the image for a larger view)
A very important step is labeling the sources. This is a requirement because without this step you would not be able to repeat the process. When you create a build that will be sent for production, you need to be able to recreate that exact same build in order to maintain the deployed code. This step will be different based on the tool you are using for your source control provider. There are many different MSBuild tasks available for download to achieve this for various source control providers. See the "MSBuild and WiX Resources" sidebar for more detail.
Anatomy of the MSBuild Script
In this section, I'll introduce the MSBuild script that can be used to build and package the sample application. The overall structure is very similar to how managed projects are built. That is, you'll define a project file that contains what should be processed, and you import another project file, Sedodream.Package.targets, that defines the process flow. Think of these processes as sentences. Your packaging project file defines the nouns and the imported file defines the verbs. I will build the project by using the MSBuild task on the Solution file. To build the installer, the previous project file, SedodreamMSI.wproj, will be used.
When you have a process that has segregated elements, they somehow need to be brought together. This is done by defining a common set of properties, items, and targets (see Figure 11).
Figure 11 Properties and Items Used to Connect Elements
|SolutionFilePath||Property that defines the location of the solution file that will be used to build the product.|
|PackageRoot||Property that defines where the projects are built and serves as the temporary build location for WiX.|
|WiXSourceFiles||Item that contains all of the WiX files to be sent to the Candle tool for compiling.|
|AllConfigurations||Item that contains all the configurations you want this process to be built for. Each configuration should contain metadata values for both FlavorToBuild and PlatformToBuild. An example declaration is:|
<AllConfigurations Include="Release|Any CPU"> <FlavorToBuild>Release</FlavorToBuild> <PlatformToBuild>Any CPU</PlatformToBuild> </AllConfigurations>
|OtherFiles||Optional item that contains other files that should be copied to the package directory. These items should contain Destination metadata. This metadata defines the relative path of the destination to the package root. Here is a sample declaration:|
<OtherFiles Include= "..\Sedodream.MSBuild.Tasks\SampleTargets\**\*"> <Destination>Samples\</Destination> </OtherFiles>
In this process, the solution will be built and the WiX source files will be copied into that same directory. You should define your WiX source files keeping this path in mind. Regardless of where your WiX files are contained in source control, they will be built from the same directory that the product is built to. Other required files are also copied to this same directory by the CopyFilesForPackaging target.
Also in this process, the PackageRoot property will define where the projects are built and where WiX will build the installers. In the sample, the directory is at the same level as the solution file, but it could be anywhere on the build machine-though I recommend not using a network share for this location.
The main targets declared in the Sedodream.Package.targets file are shown in Figure 12. All of these targets (and many others in the targets file) have their dependencies defined in properties so you can completely change the sequence of events in the process. For example, if you needed to inject a step before the Build target executed your project file, you could simply insert the following snippet after the Import statement for the Sedodream.Package.targets file:
<BuildDependsOn> CustomBeforeBuild; $(BuildDependsOn); </BuildDependsOn>
Figure 12 Build Targets in the Sample Project
|Package||Only target in the DefaultTargets list; will be called to execute the entire process.|
|Build||Builds the solution file. The output of this build is routed to the correct directory by overriding the OutputPath property when invoking the MSBuild task.|
|CopyFilesForPackaging||Copies all necessary files that are not built to the build directories.|
|DeployPackage||Called near the end of the process. In the sample this target is empty, but in your implementation you may decide to override the target, to send your packages to the QA gate or to your deployment team.|
|Clean||Cleans up the mess made by the other targets.|
Because the BuildDependsOn property was redefined, you effectively inject the CustomBeforeBuild step into the current process without modifying the existing targets file. For a more detailed discussion of this issue, you should take a look at my June 2006 article (referenced previously).
Before creating the installer, let's have a quick look at the CoreBuild target that builds the solution (see Figure 13). This will demonstrate how target batching is used to build the solution for each defined configuration.
Figure 13 CoreBuild Target
<Target Name="CoreBuild" Inputs="%(AllConfigurations.PlatformToBuild); %(AllConfigurations.FlavorToBuild)" Outputs="%(AllConfigurations.PlatformToBuild); %(AllConfigurations.FlavorToBuild)" > <Message Text="Building for Flavor/Platform: %(AllConfigurations.FlavorToBuild)/ %(AllConfigurations.PlatformToBuild)"/> <MSBuild Projects="@(SolutionFile)" Targets="Build" Properties="OutputPath=$(PackageRoot) %(AllConfigurations.FlavorToBuild); Configuration=%(AllConfigurations.FlavorToBuild); Platform=%(AllConfigurations.PlatformToBuild)"> <Output ItemName="OutputFiles" TaskParameter="TargetOutputs"/> </MSBuild> </Target>
Specifically, the first thing to notice is the Inputs and Outputs; these are the metadata values for the AllConfigurations item that will cause the target to be executed once per platform/flavor pair. Then, the MSBuild task is called to build the solution file with all the appropriate properties. The OutputPath is provided because I want to redirect the output to another directory.
Putting It All Together
At this point, I have discussed how to create an installer using WiX, some advanced MSBuild concepts, and the structure of the MSBuild packing script. The only remaining issue with regard to creating the installer for the sample project is creating the MSBuild project file to define the required properties. This is surprisingly simple. Finally, I'll show you how to customize the process to suit your needs.
Figure 14 shows the MSBuild file, Sedodream.Package.dproj, that will drive the process for the sample project. As discussed earlier, the main purpose of this project file is to describe what needs to be built. The imported file, Sedodream.Package.targets, knows all the details about how to create the final product, and you can also customize the process by injecting steps into the build process.
Figure 14 Sedodream.Package.dproj MSBuild Project
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Package"> <!-- Required properties by the deployment.targets file --> <PropertyGroup> <SolutionFilePath>..\Sedodream.MSBuild.sln</SolutionFilePath> <PackageRoot>..\Package\</PackageRoot> </PropertyGroup> <ItemGroup> <WiXSourceFiles Include="Sedodream.wxs"/> <OtherFiles Include="..\Sedodream.MSBuild.Tasks\ SampleTargets\**\*"> <Destination>Samples\</Destination> </OtherFiles> <!-- Copy the license to the BaseSearchPath so it can be included into the installer --> <OtherFiles Include="License.rtf"> <Destination></Destination> </OtherFiles> <!-- Copy the custom installer bitmaps --> <OtherFiles Include="bitmaps\dlgbmp.bmp"> <Destination>bitmaps\</Destination> </OtherFiles> </ItemGroup> <!-- Define all the configurations that you want to build here --> <ItemGroup> <AllConfigurations Include="Debug|x86"> <FlavorToBuild>Debug</FlavorToBuild> <PlatformToBuild>Any CPU</PlatformToBuild> </AllConfigurations> <AllConfigurations Include="Release|Any CPU"> <FlavorToBuild>Release</FlavorToBuild> <PlatformToBuild>Any CPU</PlatformToBuild> </AllConfigurations> </ItemGroup> <PropertyGroup> <DropLocation>C:\Data\Drops\MSBuild-Wix\</DropLocation> </PropertyGroup> <!-- Import the deployment target to do all the work for us --> <Import Project="Sedodream.Package.targets"/> </Project>
In this project file, you see the required SolutionFilePath property defined. This is the path to the solution file that will be used to build the product. Also, you see the declaration of the WixSourceFiles item, which includes the set of files that will be used to compile your installer. These are the files that will be sent to the Candle.exe tool for compiling.
Along with this is the declaration of the OtherFiles item, which contains files that should be copied to the location that will be used to create the installer. These files will be copied to the BaseInputPath that will be used to create the installer. You can have them copied to any location underneath that by using the Destination metadata value. In this example, I am copying custom bitmaps to be used to generate the installer into the bitmaps folder. When the Light.exe tool is invoked to create the MSI file, it will pick up files that are in this directory before any default files that are located in the local directory.
Note the declaration of the DropLocation property. By declaring this property, all the files in the PackageRoot will be copied to a directory in the DropLocation. It's helpful to have the binaries and installer files in one location, in case you need to make some quick modifications.
To see the build and package executed for the sample project, open a Visual Studio 2005 command prompt and navigate to the projects deployment folder. Then execute the following command:
Lots of output is generated to the command prompt, and you will find two directories, Debug and Release, under the Package directory. Each of these directories will contain the generated installer and its associated files.
Customizing the Process
When you set up this process for your product, you will need to create a file similar to the SedodreamPackage.dproj file. You can place your customizations directly inside that file. It shouldn't be necessary to change the Sedodream.Package.targets file itself. If you want to inject steps, you can follow the process described previously. If you want to redefine targets, you can simply override them by redeclaring the target after the import statement. For example, the Sedodream.Package.targets file defines a target, GetLatest, but it is empty. In this target you should place the necessary tasks to get the latest sources from your repository. Here is an outline of what it might look like in your project:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Package"> <!-- Your properties and items defined here --> <!-- Import the deployment target to do all the work for us --> <Import Project="Sedodream.Package.targets"/> <!-- Place overriding customizations after this point --> <Target Name="GetLatest"> <Message Text="Getting latest sources"/> <!-- Insert tasks to get latest from source control --> </Target> </Project>
Figure 15 provides a list of important targets that are defined in the Sedodream.Package.targets file. You can customize these targets to suit your needs.
The remaining outstanding topic is how to automate this process, which is now extremely simple. All you have to do is automate the execution of MSBuild.exe on the deployment file, in this case SedodreamPackage.dproj. How this is accomplished will depend on how you are currently generating public builds. If you are using Visual Studio Team Foundation Server, you can use Team Build to perform this for you. You can use the MSBuild task in your TFSBuild.proj file. If you are using some other technologies, it is likely that they have native support for MSBuild. If you are not employing any of these types of technologies, you can use the Windows Scheduler to execute this on a timed basis.
In this article, I've shown how to create an automated build and package process based on WiX toolset. Once you have completed this integration you can build and package your products in a reliable and repeatable manner. This is very important for applications that are being sent into deployment.
If WiX is not your installer technology, you can still use the ideas and files, provided here with some modifications. All installer technologies that I know of support some form of a command-line execution. You would employ this in order to create your installers using these other technologies. Some third-party installer technologies have also shipped with MSBuild tasks and targets, which may assist you in recreating this process.
MSBuild and WiX Resources
- Inside MSBuild: Compile Apps Your Way with Custom Tasks for the Microsoft Build Engine
- Using the WiX Toolset to Integrate Setup into Your Development Process
- WiX Project Home
- rRob Mensching’s Blog (WiX Project Lead)
- Justin Rockwood’s Blog (WiX Developer)
- Sayed Hashimi’s Blog
- MSDN MSBuild Forum
- MSBuild Team Blog
- MSBuild MSDN Documentation
- MSBuild MSDN Reference
- MSBuild WiKi
Sayed Ibrahim Hashimi has a computer engineering degree from the University of Florida. He is a developer and architect in Jacksonville, Florida. He is an expert in the financial, education, and collection industries. He works with .NET technologies at Latitude Software.