In the first installment of this series I discussed an introduction to WiX. In the second installment, I talked about the issues related to creating .WXS files with TALLOW.EXE and how you have to manually maintain those files by hand, which can be tedious to the extreme. In this final installment, I will present a tool I wrote to solve those problems.
Since all the binaries’ names in WiX all play on parts that make up a candle, I knew I was going to have to continue the tradition. With my goal of replacing TALLOW.EXE (rendered beef or mutton fat and rarely used to make candles any more), I named my program PARAFFIN.EXE (a petroleum byproduct and what most candles are made out of in the modern world).
My goal for PARAFFIN.EXE was that it would build immediately consumable WiX fragments with a minimum of fuss on your part. I wanted PARAFFIN.EXE to meet the following requirements for initially creating a .WXS fragment for a directory:
- PARRAFIN.EXE created unique values to the Component, Directory, and File elements Id attribute so you do not have to worry about conflicts across large projects
- PARAFIN.EXE creates a ComponentGroup element in the output file with all Component elements in the file automatically specified with ComponentRef values
- You can optionally exclude specific file extensions from being added to the .WXS fragment
- You can optionally exclude directories from inclusion by specifying a partial name
- You can optionally specify if you want GUID values automatically generated for all components
- You can optionally specify multiple files per Component (the default is one file per component)
- You can optionally specify that you do not want to recurse directories other than the one specified
- You can optionally specify an alias for the directory name when setting the File element Source attribute so you do not have hard coded drive and directory names in the output .WXS file
After you’ve created a .WXS fragment with PARAFFIN.EXE, you don’t want to have to edit the fragment manually, so I wanted PARAFFIN.EXE to meet the following requirements for creating an updated output file from an existing .WXS fragment:
- The updated output is written to a .PARAFFIN file so the original .WXS fragment is not disturbed
- All command line options specified when creating the initial .WXS fragment are automatically set when updating a file created by PARAFFIN.EXE
- Any new directories and files found are automatically added to the output file
- Any directories and files that are no longer part of the directory structure are removed from the output file
As you can tell from both sets of requirements, PARAFFIN.EXE makes it very easy to create the initial .WXS file and, most importantly, it does as much as is safely possible to provide you a way to avoid all the tedious manual editing of .WXS fragments. Before I get too far into showing how to use PARAFFIN.EXE, I want to reiterate from the previous installment that you can break component rules with PARAFFIN.EXE. While I can’t stop you from running PARAFFIN.EXE from your build and creating all your .WXS fragments every time you build, I would strongly recommend you don’t do that because you’ll have different GUIDs for your components and completely break all component rules. I would also recommend you stay with the PARAFFIN.EXE defaults of one file per component so you can remove a single file without invalidating an entire component. That will make your installs slower than multiple files per component, but it helps you avoid breaking the component rules for large groups of files at a time. Finally, when using PARAFFIN.EXE during the heat of development, it’s an extremely good practice to uninstall any previous version of your application before installing the current build. This helps ensure the previous version removed all the files that might have been deleted in the new installation.
Using a small example is probably the best way to show you how to create and maintain WiX fragments with PARAFFIN.EXE. Let’s say I have a directory called .Example with two files in it, HelpFile.chm and ReadMeFirst.txt. I also have a sibling directory of .Example, called .Install, where the installation code is build from.
The first step that I’ll want to do is to create the initial .WXS file for the .Example directory with PARAFFIN.EXE. In a Command Prompt window, I’ll switch to the .Example directory and run the following command line (one line):
Paraffin.exe -dir . -custom EXAMPLE -g -alias ..Example –ext .WXS ExampleFragment.wxs
The –dir switch tells PARAFFIN.EXE what directory I want to process. The –custom switch specifies the custom value PARAFFIN.EXE will use to build up the Id attributes for the ComponentGroup, Directory, and File elements for this fragment file. Specifying –g will automatically generate GUID values for all components. The –alias switch will cause .Example to be substituted for the starting directory in the File element’s Source attribute. The –ext switch tells PARAFFIN.EXE to ignore all files with the extension .WXS. Obviously, ExampleFragment.wxs will be the output file. After running the above command, ExampleFragment.wxs looks like the following (note that I wrapped some lines to get them to fit better):
<?xml version=”1.0″ encoding=”utf-8″?>
<Wix >
<!–<CommandLineOptions>
<Producer>Autogenerated by Paraffin –
Wintellect – John Robbins –
john@training.atmosera.com</Producer>
<WARNING>Manual changes to this file may cause incorrect
behavior.</WARNING>
<CreatedOn>10/17/2007 6:03 PM</CreatedOn>
<Directory>.</Directory>
<Custom>EXAMPLE</Custom>
<DirAlias>..Example</DirAlias>
<Increment>1</Increment>
<Guids>true</Guids>
<Multiple>false</Multiple>
<Norecurse>false</Norecurse>
<ExtensionExcludes>
<Ext>.WXS</Ext>
</ExtensionExcludes>
<DirExcludes />
<NextDirectoryNumber>1</NextDirectoryNumber>
<NextComponentNumber>2</NextComponentNumber>
</CommandLineOptions>–>
<Fragment>
<ComponentGroup Id=”group_EXAMPLE”>
<ComponentRef Id=”comp_EXAMPLE_0″ />
<ComponentRef Id=”comp_EXAMPLE_1″ />
</ComponentGroup>
<DirectoryRef Id=”TARGETDIR”>
<Directory Id=”dir_Example_0″ Name=”Example”>
<Component Id=”comp_EXAMPLE_0″
DiskId=”1″
KeyPath=”yes”
Guid=”DE4C9C18-1B4B-42E0-A199-E530462A7561″>
<File Id=”file_EXAMPLE_0″
Name=”HelpFile.chm”
Source=”..ExampleHelpFile.chm” />
</Component>
<Component Id=”comp_EXAMPLE_1″
DiskId=”1″
KeyPath=”yes”
Guid=”A5B3009D-93CC-4853-B945-C9D177157997″>
<File Id=”file_EXAMPLE_1″
Name=”README_1.TXT”
LongName=”ReadmeFirst.txt”
Source=”..ExampleReadmeFirst.txt” />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
</Wix>
Most of the file is your standard .WXS fragment, but the interesting part is the comment after the start of the WiX element. PARAFFIN.EXE uses that comment element to identify the file as one it produced. If you look through the comment you can see how PARAFFIN.EXE stores the various command line options so it knows what to apply when you update the file. If the comment section is missing or corrupt, PARAFFIN.EXE cannot work with the file.
As development progresses, suppose you add one new file to the .Example directory, AdvancedHelp.chm, and remove ReadmeFirst.txt. To generate the ExampleFragment.PARAFFIN file with the updates, you’d go back to the .Examples directory in a Command Prompt and run the following command:
Paraffin.exe -update ExampleFragment.wxs
The ExampleFragment.PARAFFIN file looks like the following:
<?xml version=”1.0″ encoding=”utf-8″?>
<Wix >
<!–<CommandLineOptions>
<Producer>Autogenerated by Paraffin –
Wintellect – John Robbins –
john@training.atmosera.com</Producer>
<WARNING>Manual changes to this file may cause
incorrect behavior.</WARNING>
<CreatedOn>10/17/2007 6:21 PM</CreatedOn>
<Directory>.</Directory>
<Custom>EXAMPLE</Custom>
<DirAlias>..Example</DirAlias>
<Increment>1</Increment>
<Guids>true</Guids>
<Multiple>false</Multiple>
<Norecurse>false</Norecurse>
<ExtensionExcludes>
<Ext>.WXS</Ext>
</ExtensionExcludes>
<DirExcludes />
<NextDirectoryNumber>1</NextDirectoryNumber>
<NextComponentNumber>3</NextComponentNumber>
</CommandLineOptions>–>
<Fragment>
<ComponentGroup Id=”group_EXAMPLE”>
<ComponentRef Id=”comp_EXAMPLE_0″ />
<ComponentRef Id=”comp_EXAMPLE_2″ />
</ComponentGroup>
<DirectoryRef Id=”TARGETDIR”>
<Directory Id=”dir_Example_0″ Name=”Example”>
<Component Id=”comp_EXAMPLE_2″
DiskId=”1″
KeyPath=”yes”
Guid=”6743A4B6-455C-4F75-9B55-86F59C6DFE03″>
<File Id=”file_EXAMPLE_2″
Name=”ADVANC_1.CHM”
LongName=”AdvancedHelp.chm”
Source=”..ExampleAdvancedHelp.chm” />
</Component>
<Component Id=”comp_EXAMPLE_0″
DiskId=”1″
KeyPath=”yes”
Guid=”DE4C9C18-1B4B-42E0-A199-E530462A7561″>
<File Id=”file_EXAMPLE_0″
Name=”HelpFile.chm”
Source=”..ExampleHelpFile.chm” />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
</Wix>
As you can see, PARAFFIN.EXE did the right operations and removed the Component and File elements associated with ReadmeFirst.txt, added those for AdvancedHelp.chm, and left all the others alone. Once you’ve read over the changes in the .PARAFFIN file, you can either do a compare and merge in the changes or just rename the .PARAFFIN file to the .WXS file. If you’re looking for an excellent standalone difference and merge tool, I highly recommend SourceGear’s outstanding (and free) DiffMerge.
One big hint when using PARAFFIN.EXE is that you should run any updates from the same directory as you did when initially creating it. If you look closely at the comment element for the above files, you’ll see that I store the Directory element exactly as it was passed in. I played around with storing calculated values, but would always run into potential hard coded path values. In the end I felt the best solution was to avoid the hard coding.
If you want to see a complete example of a WiX installation that was developed with PARAFFIN.EXE, see the .Install directory in the code download. That is an installation for the PARAFFIN project itself. I first ran the batch file FirstParaffinRun.cmd to create the fragment files for the .Install and .Paraffin directories. As I built up the installation and the code, I ran UpdateParaffinRun.cmd to get any additional files added into my two fragments.
The only requirement for PARAFFIN.EXE is that you have the .NET Framework 3.5 installed on the machine. I built the code with Visual Studio 2008 Beta 2. As you’ll see in the next paragraph, there’s literally no way I will work with XML again without Linq for XML and the awe inspiring XElement class and friends.
Originally, I started thinking about implementing PARAFFIN.EXE with Visual Studio 2005 and .Net 2.0. As I was struggling through the standard XmlDocument classes, I realized it was going to be quite a bit of work to get everything implemented. Fortunately, Visual Studio 2008 Beta 2 shipped so I could look at Linq as a solution. As I worked my way through some prototyping I realized that Linq and Linq for XML had to be one of the biggest boosts in productivity I’ve seen in years. According to the new Code Metrics in Visual Studio 2008, there are only 533 lines of code in PARAFFIN.EXE. As an interesting aside, 257 of the 533 lines in PARAFFIN.EXE are related to command line argument processing. My guess is that I would have had at least 1,500 lines of code using .NET 2.0.
Most of the code is straightforward. In fact, it only took me a day to implement the new .WXS fragment creation code. Updating existing fragments took about two days. In looking at the different ways to coordinate walking both the directory structure and the XML structure, I felt it was easiest to simply walk both of them at the same time instead of using any sort of two pass system.
As I enumerate the subdirectories, I use a Linq query to look for the matching Directory element from the current position in the input XML file. Once I have the Directory node, I do a similar operation to look for the files on disk as well as in the input XML file. The biggest method in the project is UpdateFilesInDirectoryNode, (in Main.CS) which does all the work to coordinate between the files on disk and what was in the input .WXS fragment. It’s worth reading through the code for that method to see how I used Linq queries for filtering out excluded extensions and looking for a file in the XML document.
PARAFFIN.EXE has certainly made maintaining my WiX installations far easier and I hope it will for yours. While PARAFFIN.EXE has worked for me, I’m sure it could use more testing. Please report any bugs you find, or feature enhancement requests, either as comments to this blog entry or to me directly at john@training.atmosera.com.
You can download the code and PARAFFIN.EXE here. Please note that you need the .NET Framework 3.5 installed on your machine for PARAFFIN.EXE to work.
Edit May 16, 2009: Updated the Paraffin download link to point to the updated 3.0 version.