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:
- 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
5 Comments
I bet that made the build manager a happy lad!!
I’ve marked this one as a keeper in my list btw. Top post
Good stuff, thanks!
The only modification I have made: I wanted the file name for the MST be + “_” + .mst
2nd try:
[MSI File Name] + “_” + [environment name]
Eg.
MySetup_Development.mst
MySetup_Production.mst
Does anyone have a solution to clean up the temp files? They are still locked after the task has finished…
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
[...] 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 [...]