Now that our journey has begun and we have a basic Wix installer under our belt, we need to get the rest of our web application's files into the installer. Creating all the <Directory> and <Component> and <File> elements by hand gets very tedious especially if you have a lot of directories and files in your projects. Thankfully there is a utility included with Wix called Tallow that takes care of most of the busy work for you. In this post I will show you how to use Tallow against your release folder to do a one time generation of Wix XML for your project. Don't have a release folder yet? Let's begin with automating the creation of one.
Build Your Release Folder
Here is some NAnt build automation that copies files to a release folder. The layout of the files in the release folder should match directory structure that we wish to deploy.
<target name="release-properties"> <property name="release.dir" value="${path::combine(path::get-full-path(build.dir),'Release')}"/> <property name="wix.dir" value="${tools.dir}\wix"/> <property name="htmldocs.release.dir" value="${path::combine(path::get-full-path(release.dir),'docs')}"/> </target> <target name="copy-to-release" depends="release-properties"> <mkdir dir="${release.dir}" failonerror="false"/> <copy todir="${release.dir}"> <fileset basedir="${src.dir}"> <include name="*" /> <exclude name="*.sln"/> <exclude name="*.suo"/> <exclude name=".svn"/> <exclude name="*Resharper*"/> </fileset> </copy> <mkdir dir="${htmldocs.release.dir}"/> <copy file="${docs.dir}\pleats.chm" tofile="${htmldocs.release.dir}\pleats.chm" /> </target>
This is for a very simple project with just source code and documentation to copy. You will likely have a bit more going on. The <copy> task in NAnt is pretty powerful. Take a little time to learn all that it has to offer (like filesets and filterchains.) Lets invoke the target and see what happens.
>cd (project root)
>.\tools\nant\nant copy-to-release
(...)
copy-to-release:
[copy] Copying 50 files to 'C:\projects\pleats\build\Release'.
[mkdir] Creating directory 'C:\projects\pleats\build\Release\docs'.
[copy] Copying 1 file to 'C:\projects\pleats\build\Release\docs\pleats.chm'
I don't really feel like like writing all the elements for the 51 files in the project, thankfully there is a better way.
Using Tallow
Tallow is a tool that comes with the Wix binaries that is there to generate Wix content for existing directory structures It is not intended to be integrated with your build automation. You basically just run the tool against a directory and it will generate XML suitable for copy/paste into the pleats.wxs file.
(project root)>cd wix
..\tools\wix\tallow.exe -nologo -d ..\build\Release > pleats-release.wxs
Open the pleats-release.wxs file and you see:
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi"> <Fragment> <DirectoryRef Id="TARGETDIR"> <Directory Id="directory0" Name="Release"> <Component Id="component0" DiskId="1" Guid="PUT-GUID-HERE"> <File Id="file0" Name="admin.asp" Source="C:\projects\pleats\build\Release\admin.asp" /> <File Id="file1" Name="admin2.asp" Source="C:\projects\pleats\build\Release\admin2.asp" /> <!-- file2 <-> file47 elided for brevity --> <File Id="file48" Name="view.asp" Source="C:\projects\pleats\build\Release\view.asp" /> <File Id="file49" Name="views.asp" Source="C:\projects\pleats\build\Release\views.asp" /> </Component> <Directory Id="directory1" Name="docs"> <Component Id="component1" DiskId="1" Guid="PUT-GUID-HERE"> <File Id="file50" Name="pleats.chm" Source="C:\projects\pleats\build\Release\docs\pleats.chm" /> </Component> </Directory> </Directory> </DirectoryRef> </Fragment> </Wix>
Sprinkle with GUIDs and component names
Ah yes, PUT-GUID-HERE. Looks like we need to immortalize a couple of GUIDs to uniquely identify the two components in this project. I like to use the Create GUID tool in VS.Net : Tools -> Create GUID. For your own projects you should always use your own GUIDs not ones "immortalized" by me. Here is the same XML with GUIDs added and the components given more appropriate identifiers.
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi"> <Fragment> <DirectoryRef Id="TARGETDIR"> <Directory Id="directory0" Name="Release"> <Component Id="component0" DiskId="1" Guid="{0A8A89AF-BE9D-4164-BECE-6393F3E7B904}"> <File Id="file0" Name="admin.asp" Source="C:\projects\pleats\build\Release\admin.asp" /> <File Id="file1" Name="admin2.asp" Source="C:\projects\pleats\build\Release\admin2.asp" /> <!-- file2 <-> file47 elided for brevity --> <File Id="file48" Name="view.asp" Source="C:\projects\pleats\build\Release\view.asp" /> <File Id="file49" Name="views.asp" Source="C:\projects\pleats\build\Release\views.asp" /> </Component> <Directory Id="directory1" Name="docs"> <Component Id="component1" DiskId="1" Guid="{A93FFF75-FD44-416e-A718-24B1A6F43195}"> <File Id="file50" Name="pleats.chm" Source="C:\projects\pleats\build\Release\docs\pleats.chm" /> </Component> </Directory> </Directory> </DirectoryRef> </Fragment> </Wix>
Cut out absolute paths
Next we need to remove that nasty absolute path from each Source attribute of the File elements. To decouple my build file from my development machine's file paths I like to use a Wix preprocessor directive $(env.pleats.dir) to replace a path with an environment variable that I set using the NAnt <setenv> task. We already saw an example of doing this in the previous post.
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi"> <Fragment> <DirectoryRef Id="TARGETDIR"> <Directory Id="directory0" Name="Release"> <Component Id="component0" DiskId="1" Guid="{0A8A89AF-BE9D-4164-BECE-6393F3E7B904}"> <File Id="file0" Name="admin.asp" Source="$(env.pleats.dir)\admin.asp" /> <File Id="file1" Name="admin2.asp" Source="$(env.pleats.dir)\admin2.asp" /> <!-- file2 <-> file47 elided for brevity --> <File Id="file48" Name="view.asp" Source="$(env.pleats.dir)\view.asp" /> <File Id="file49" Name="views.asp" Source="$(env.pleats.dir)\views.asp" /> </Component> <Directory Id="directory1" Name="docs"> <Component Id="component1" DiskId="1" Guid="{A93FFF75-FD44-416e-A718-24B1A6F43195}"> <File Id="file50" Name="pleats.chm" Source="$(env.pleats.dir)\docs\pleats.chm" /> </Component> </Directory> </Directory> </DirectoryRef> </Fragment> </Wix>
Grab what we need and put it into pleats.wxs
You probably noticed that the XML generated is encapsulated in a Wix Fragment. Wix elements with Identifiers can be referenced from other .wxs files (see DirectoryRef for an example of this.) To keep things simple we are just going to cut and paste the bit we need into pleats.wxs and update the Feature element to reference to two components in our project.
Here is updated pleats.wxs file:
<?xml version='1.0' encoding='Windows-1252'?> <Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'> <Product Name="Pants Enterprises Pleats" Id="{A263E591-B290-4db2-BF08-0C6FF24959A3}" Language="1033" Codepage="1252" Version="2.7.0" Manufacturer="Pants Enterprises Inc."> <Package Id="????????-????-????-????-????????????" Keywords="Pleats" Description="Pants Enterprises - Pleats" Comments="A web application for pants users." Languages="1033" Compressed="yes" SummaryCodepage="1252" /> <Media Id="1" Cabinet="Pleats.cab" EmbedCab="yes" DiskPrompt="CD-ROM #1" /> <Property Id="DiskPrompt" Value="Pants Enterprises - Pleats Installation [1]" /> <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder" Name="PFiles"> <Directory Id="dirDovetailPrograms" Name="Dovetail" LongName="Pants Enterprises"> <Directory Id="INSTALLDIR" Name="Pleats"> <Component Id="pleatsWebApplication" DiskId="1" Guid="{2431C807-01E6-4f16-9664-23CAE4E0BDF9}"> <File Id="file0" Name="admin.asp" Source="$(env.pleats.dir)\admin.asp" /> <File Id="file1" Name="admin2.asp" Source="$(env.pleats.dir)\admin2.asp" /> <!-- file2 <-> file47 elided for brevity --> <File Id="file48" Name="view.asp" Source="$(env.pleats.dir)\view.asp" /> <File Id="file49" Name="views.asp" Source="$(env.pleats.dir)\views.asp" /> </Component> <Directory Id="directory1" Name="docs"> <Component Id="pleatsDocumentation" DiskId="1" Guid="{EEC96333-0B40-49f8-A6BA-C7BDC53241E0}"> <File Id="file50" Name="pleats.chm" Source="$(env.pleats.dir)\docs\pleats.chm" /> </Component> </Directory> </Directory> </Directory> </Directory> </Directory> <Feature Id="Complete" Level="1"> <ComponentRef Id="pleatsWebApplication" /> <ComponentRef Id="pleatsDocumentation" /> </Feature> <UIRef Id="WixUI_Minimal" /> </Product> </Wix>
Test It
Now all there is to do is build the installer and make sure all the files and documentation are getting installed.
(project root)>.\tools\nant\nant build-installer
After running the installer with all defaults I see this directory on my system.
Conclusion
Tallow is great for when your application is all ready to go and you want to generate all the XML you need like we did above. Trouble comes knocking when you are in a more iterative process with an existing installer being updated with components, directories, and files being removed or added. When updating an existing installer I have always just made note of what files needed to be added or removed and manually edited the XML to suit. If you are are running a large project whose files change a lot there is a Tallow like tool from John Robbins called Paraffin that I have not used but advertises some change management features.





Post new comment