MSBuild task to generate MSI Transform files from XML

In 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:

  1. They’re a binary format
  2. 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

GenerateMsiTransform MSBuild Task Overview

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

5 Comments

  1. Steve
    Posted March 11, 2008 at 11:10 pm | Permalink

    I bet that made the build manager a happy lad!!

    :)

    I’ve marked this one as a keeper in my list btw. Top post :)

  2. thijs
    Posted May 21, 2008 at 7:52 pm | Permalink

    Good stuff, thanks!

    The only modification I have made: I wanted the file name for the MST be + “_” + .mst

  3. thijs
    Posted May 21, 2008 at 7:53 pm | Permalink

    2nd try:
    [MSI File Name] + “_” + [environment name]

    Eg.
    MySetup_Development.mst
    MySetup_Production.mst

  4. thijs
    Posted June 19, 2008 at 6:38 pm | Permalink

    Does anyone have a solution to clean up the temp files? They are still locked after the task has finished…

  5. Si
    Posted March 5, 2009 at 3:04 pm | Permalink

    Can you change the UpgradeCode, Product Name, Component Guids, etc of the MSI using this approach?

    That would be very useful – we have similar requirement except not to modify config files (they can be done via xmlconfig and user input) but rather for Live, Test and Training installs, sometimes Live and Training (or Test and Training) are on the same server (thus the need for difference upgrade codes, product name, install locations, etc). But of course still have the one source to maintain.

    The caveat to your approach appears that you maintain the same GUID’s, so you can’t have multiple versions installed.

    FWIW – this is the approach I took:
    http://stackoverflow.com/questions/471424/wix-tricks-and-best-practices/578340#578340


One Trackback

  1. [...] 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 [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*