MSBuild task to generate MSI Transform files from XML
March 11, 2008 at 10:05 pm | In TFSBuild.com | 5 CommentsIn my previous post I described how you can use MSI Transform (*.mst) files to separate environment-specific configuration settings from a generic MSI package.
Although this provides a level of flexibility in your configuration, there are two down-sides of Transform files:
- They’re a binary format
- You can’t “edit” or “view” a transform file. You have to apply it or re-create it to see what changes it includes.
Fortunately we can use the Microsoft Windows Installer Object Library (c:\windows\system32\msi.dll) to open MSI “databases” and create transform files.
Credits go again to Alex Shevchuk - From MSI to WiX - Part 7 - Customising installation using Transforms for showing us how to achieve this with VbScript. Essentially all I’ve done is taken Alex’s example and using Interop.WindowsInstaller.dll I’ve implemented an MSBuild task.
The <GenerateMsiTransform> MSBuild Task
Download the source code & example transforms.xml here (~7Kb Zipped VS2008 Solution)
The <GenerateMsiTransform> MSBuild task generates MSI Transform (*.mst) files with updated configuration properties for a specified MSI package from an XML configuration file.
This removes the need to manually create transforms using the ‘Orca’ MSI editing tool.
Since the generation of transforms can now be automated, it also means that environment configurations can be stored & versioned under source control.
How it works
Because the WindowsInstaller is an unmanaged COM object and there’s no functionality to “close” a database, the file handle remains open. To work around this we have to create temporary copies of the original MSI file. The workflow goes something like this:
- Read each <transform> element from the ConfigFile
- For each transform:
- Make the changes to a copy of the original MSI file
- Copy the MsiFile to a temporary file
- Update the properties / values with the values from the current <transform> element
- Save the changes
- Copy the resultant MSI to a new temporary file
- Diff the changed MSI with the original MSI
- Open original MSI
- Open changed MSI
- Generate a transform file by diff’ing the two
Using the task
Although the task could be used as a stand-alone task invoked by msbuild.exe, the ideal place for it is part of a Team Build script.
By overriding the “AfterCompile” target in TFSBuild.proj, you can create the Transform files just after the installer is built & it will get copied up to the Team Build drop directory.
<UsingTask TaskName=”GenerateMsiTransform” AssemblyFile=”$(MSBuildExtensionsPath)\GenerateMsiTransformTask.dll” />
<Target Name=”AfterCompile”>
<PropertyGroup>
<MsiFile>$(Outdir)\Project.Deployment.msi</MsiFile>
<TransformConfigFile>$(SolutionRoot)\transforms.xml</TransformConfigFile>
</PropertyGroup>
<GenerateMsiTransform MsiFile=”$(MsiFile)” ConfigFile=”$(TransformConfigFile)”>
<Output ItemName=”TransformFiles” TaskParameter=”Transforms” />
<Output ItemName=”TempFiles” TaskParameter=”TemporaryFiles” />
</GenerateMsiTransform>
<Message Text=”Created transform files: @(TransformFiles)” />
<Message Text=”Created temporary files: @(TempFiles)” />
</Target>
Available Properties
| Property | Description |
| MsiFile | The base MSI installer that you want to generate the transform for |
| ConfigFile | The configuration file that contains the properties to add to the transform |
| Transforms (Output) | An output property that includes the path to each of the transform files created |
| TemporaryFiles (Output) |
An output property that includes the path to each of the temporary MSI files created as part of the generation process. |
Transform Configuration File (transforms.xml)
The Transform configuration file is designed to be as simple as possible. Each <property /> element specifies a key name & value. Here’s an example:
<?xml version=”1.0″ encoding=”utf-8″?>
<transforms>
<transform name=”Development”>
<property name=”CONFIG_CONNSTRING” value=”Data Source=DEVSQLSERVER;Initial Catalog=HelloWorldDevelopment;Integrated Security=True” />
<property name=”CONFIG_DEBUGENABLED” value=”True” />
</transform>
<transform name=”Test”>
<property name=”CONFIG_CONNSTRING” value=”Data Source=TESTSQLSERVER;Initial Catalog=HelloWorldTest;Integrated Security=True” />
<property name=”CONFIG_DEBUGENABLED” value=”True” />
</transform>
</transforms>
I hope this helps somebody out there - please leave a comment if you find it useful or extend it in interesting ways. For more interesting Team Build recipes like this, be sure to have a look at TFSBuild.com
Customising your WiX MSI with Transform files
March 11, 2008 at 9:23 pm | In TFSBuild.com | 1 CommentIn the different applications development areas that I’ve worked in, the typical process for getting a product installed is something like this:

- Developers are responsible for building the installation package (a WiX MSI is preferred)
- Then it’s left up to IT to update the config files
- And IT deploy the installer to a server or end-users.
Step #2 is where things usually come undone. Some common mistakes I’ve seen people make are:
- There’s no configuration screens in the installer to specify configuration.
- Sometimes the IT guys rip apart the package and re-package it.
- Sometimes the installer doesn’t deal with configuration at all - and the IT guys are left hand-editing XML configuration files on servers.
One of the really nice ways you can avoid these mistakes is to make use of Windows Installer Transform Files for environment-specific configuration settings.
A transform is a collection of changes applied to an installation. By applying a transform to a base installation package, the installer can add or replace data in the installation database.
Credits go to Alex Shevch’s blog post - From MSI to WiX, Part 7 Customising installation using Transforms for describing this technique in detail.
A typical output from the developers now looks like this:
Step 1 - Parameterising your WiX script
Within your WiX script, you can refer to properties by placing square-brackets around them. e.g. [CONFIG_CONNECTIONSTRING]
<util:XmlFile Id=”SetConnString” Action=”setValue” ElementPath=”//appSettings/add[\[]@key=’MyConnectionString’[\]]/@value” Value=”[CONFIG_CONNECTIONSTRING]” File=”[INSTALLDIR]ConsoleApp.exe.config” />
Now that a property is defined in the MSI, we can set it from the command line like so:
msiexec.exe /i MyInstaller.msi CONFIG_CONNECTIONSTRING=”Data Source=(LOCAL);etc…”
By default, Windows Installer doesn’t do any verification of properties. If a property isn’t set, then it will just replace it with nothing when it comes across it. If you want to ensure that all your required properties are set, you can use a launch condition. e.g.
<Condition Message=”CONFIG_CONNECTIONSTRING variable is not set. Try setting the property from the command line or using a transform.”>Installed OR CONFIG_CONNECTIONSTRING</Condition>
Step 2 - Creating a transform file
If you were to create a transform manually, the tool you would use is ORCA. (Not to be confused with “Orcas” - the code name for Visual Studio 2008). Orca is distributed in the (large >1GB) Platform SDK download. Fortunately Aaron Stebner has made just the ORCA installer available through is blog.
Aaron Stebner’s blog - Download Orca install package (direct)
You can quickly access Orca by right-clicking any MSI file and selecting “Edit with Orca” from the context menu:
Once you’ve opened the MSI editing utility, select the “Property” table and have a look at the different properties you can set. If you’ve configured your WiX script correctly, you should be able to see the properties that you made available.
Step 2a - Manually creating a transform
- From the menu, select Transform | New Transform
- Make changes to any properties with your environment-specific configuration
- select Transform | Generate Transform…
- Type the filename of the *.mst file you want to save your differences as
- Repeat the process for each transform/environment that you need to
Now when you run your installation, you specify the transform file as a parameter:
msiexec.exe /i MyInstaller.msi TRANSFORMS=Development.mst
Conclusion
This is an excellent way to reuse a single MSI as you promote it through your dev/test/qa/prod environments, without having to know the values up-front and embed them in the MSI.
In my next post I show you how I’ve extended this technique to automatically generate transform files based on an XML configuration file using the Windows Installer COM Automation object and an MSBuild task.
Building a Release Notes Text File with Team Build
February 19, 2008 at 10:48 pm | In TFSBuild.com, Team Foundation Server | 4 CommentsThe following post describes a Team Build target that generates a text file (e.g. ReleaseNotes.txt) from Team Build’s build log and places it with the build output.
This is useful if you want to distribute your product to somebody who won’t have access to your TFS server to view the build log history.
This is an example of the output that is generated:
How it works
Team Build compiles the solution and after it performs the GetChangesetsAndUpdateWorkItems task, we download the build log (as XML) using the Build.aspx web page that TFS provides.
The build log is an XML file that contains all the associated work items and changesets that have been included in the build.
We perform an XSL transform on this file to generate the text file output that we require.
Setup: TFSBuild.proj
To actually get Team Build to generate the release notes, we need override the AfterGetChangesetsAndUpdateWorkItems target and call our new target.
In source control, checkout the TFSBuild.proj file for your build definition and insert the following snippet at the end, just before the </Project> tag.
<Target Name="AfterGetChangesetsAndUpdateWorkItems">
<CallTarget Targets="GenerateReleaseNotes" />
</Target>
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<!-- Download the build log from the Build.aspx web app and tranform to ReleaseNotes.txt-->
<Target Name="GenerateReleaseNotes">
<PropertyGroup>
<BuildDownloadBaseUri>$(TeamFoundationServerUrl)Build/Build.aspx?artifactMoniker=</BuildDownloadBaseUri>
<BuildLogFile>buildlog.xml</BuildLogFile>
<ReleaseNotesXslFile>$(MSBuildExtensionsPath)\releasenotes.xsl</ReleaseNotesXslFile>
<ReleaseNotesOutputFile>$(OutDir)ReleaseNotes.txt</ReleaseNotesOutputFile>
</PropertyGroup>
<!-- Extract the artifact ID from the BuildUri for passing to Build.aspx -->
<RegexReplace Input="$(BuildUri)" Expression="vstfs:///Build/Build/" Replacement="" Count="1">
<Output ItemName="BuildLogUriId" TaskParameter="Output" />
</RegexReplace>
<WebDownload
FileUri="$(BuildDownloadBaseUri)@(BuildLogUriId)"
FileName="$(BuildLogFile)"
UseDefaultCredentials="True" />
<Exec Command="msxsl.exe "$(BuildLogFile)" "$(ReleaseNotesXslFile)" -o "$(ReleaseNotesOutputFile)"" />
</Target>
Configuration
| Property | Description | Default |
| BuildDownloadBaseUri | The URI to download the build log file from. | $(TeamFoundationServerUrl)Build/Build.aspx?artifactMoniker= |
| BuildLogFile | Path to output the downloaded build log to. | buildlog.xml |
| ReleaseNotesXslFile | Path to the XSL transform used to generate the release notes text file. | $(MSBuildExtensionsPath)\releasenotes.xsl |
| ReleaseNotesOutputFile | The output of the XSL transform | $(OutDir)ReleaseNotes.txt |
Build Server Setup
The following pre-requisite software is required for the build server:
- MSBuild Community Tasks MSI from http://msbuildtasks.tigris.org/
- msxsl.exe copied to a directory on the PATH from Command Line Transformation Utility (msxsl.exe)
More Information
Building Sandcastle Documentation with Team Build
February 19, 2008 at 10:24 pm | In TFSBuild.com, Team Foundation Server | 8 CommentsThis post outlines how to configure Team Build, Sandcastle and the Sandcastle Help File Builder (SHFB) to generate MSDN-style documentation for assemblies.
Sandcastle produces accurate, MSDN style, comprehensive documentation by reflecting over the source assemblies and optionally integrating XML Documentation Comments. It is command line based and has no GUI front-end, project management features, or an automated build process like those that you can find in NDoc.
Sandcastle Help File Builder is a frontend for Sandcastle that provides graphical and command line based tools to build a help file in an automated fashion.
How it works
The usual way to configure SHFB is to open the GUI and create a SHFB project file from your solution file with your desired configuration. Then you’re supposed to check this file into source control and maintain it each time you add a new assembly.
This post outlines a possibly simpler approach - it uses a common SHFB project template file and passes the assemblies that Team Build generates as arguments to the SandcastleBuilderConsole.exe program. This means it automatically includes all assemblies that have been built.
Once the documentation has been built, it is copied to the Team Build drop location along with the binaries.
Setup: TFSBuild.proj
To actually get Team Build to generate the documentation, we need override the GenerateDocumentation target and insert our new target into the project.
In source control, checkout the TFSBuild.proj file for your build definition and insert the following snippet at the end, just before the </Project> tag.
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Target Name="GenerateDocumentation">
<PropertyGroup>
<SandcastleBuidlerPath>C:\Program Files\EWSoftware\Sandcastle Help File Builder\SandcastleBuilderConsole.exe</SandcastleBuidlerPath>
<SandcastleBuilderProjectTemplateFile>$(MSBuildExtensionsPath)\template.shfb</SandcastleBuilderProjectTemplateFile>
<SandcastleBuilderProjectFile>$(MSBuildProjectDirectory)\project.shfb</SandcastleBuilderProjectFile>
<SandcastleBuilderArguments>-assembly="$(OutDir)*.dll" -outputpath="$(OutDir)."</SandcastleBuilderArguments>
<!-- -dependency="$(SolutionRoot)\References\*.dll" -->
<!-- HACK: For some reason the -outputpath argument requires a '.' on the end,
otherwise you get BUILD FAILED: Illegal characters in path. -->
</PropertyGroup>
<Error Text="Sandcastle Help File Builder is not found at $(SandcastleBuidlerPath)." Condition="!Exists('$(SandcastleBuidlerPath)')" />
<Error Text="Sandcastle Help File Builder project template is not found at $(SandcastleBuilderProjectTemplateFile)."
Condition="!Exists('$(SandcastleBuilderProjectTemplateFile)')" />
<Copy SourceFiles="$(SandcastleBuilderProjectTemplateFile)" DestinationFiles="$(SandcastleBuilderProjectFile)" />
<XmlUpdate XPath="/project/HelpTitle" XmlFileName="$(SandcastleBuilderProjectFile)" Value="Documentation for $(BuildDefinition)" />
<XmlUpdate XPath="/project/HtmlHelpName" XmlFileName="$(SandcastleBuilderProjectFile)" Value="$(BuildDefinition)" />
<XmlUpdate XPath="/project/FooterText" XmlFileName="$(SandcastleBuilderProjectFile)" Value="Build Number: $(BuildNumber)" />
<Exec Command=""$(SandcastleBuidlerPath)" "$(SandcastleBuilderProjectFile)" $(SandcastleBuilderArguments)" />
</Target>
Configuration
| Property | Description | Default |
| SandcastleBuidlerPath | Path to SandcastleBuilderConsole.exe | C:\Program Files\EWSoftware\Sandcastle Help File Builder\SandcastleBuilderConsole.exe |
| SandcastleBuilderProjectTemplateFile | Path to the template SHFB project file created via the GUI | $(MSBuildExtensionsPath)\template.shfb |
| SandcastleBuilderProjectFile | Path to copy the template SHFB project file to, before modifying it for the current build | $(MSBuildProjectDirectory)\project.shfb |
| SandcastleBuilderArguments | Arguments to pass to SandcastleBuilderConsole.exe | -assembly="$(OutDir)*.dll" -outputpath="$(OutDir)." |
Build Server Setup
The following pre-requisite software is required for the build server:
- Sandcastle Jan 2008 Release from Sandcastle project on Codeplex
- Sandcastle Help File Builder Installer from SHFB project on Codeplex
- Html Help Workshop from Microsoft HTML Help Downloads
- MSBuild Community Tasks MSI from http://msbuildtasks.tigris.org/
Blocked CHM Help Files from a Network Share
Because the drop location is a network share, the viewing of CHM files is often blocked. This is because network shares fall into the “Intranet” security zone, which isn’t trusted.
There are two options for getting around this:
- Copy the documentation file to your local machine, right-click, select properties, select Unblock.
- Or disable blocking for the Intranet zone by creating the following registry key. (More information here)
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\HTMLHelp\1.x\ItssRestrictions] "MaxAllowedZone"=dword:00000001
TODO
- It may be necessary to cater for References or Dependent assemblies. This would be possibly via another command line argument and a <CreateItem> task to search $(SolutionRoot)\References\**\*.dll
- Namespace documentation is usually configured via the SHFB project file, I haven’t covered this in this implementation.
More Information
- Sandcastle team blog
- Sandcastle on MSDN
- Sandcastle project on Codeplex
- Sandcastle Help File Builder project on Codeplex
- MSBuild Community Tasks (there is a documentation file in the download that describes all the tasks and syntax)
- MSDN: Customizable Team Foundation Build Targets
- Microsoft HTML Help Downloads
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.
