Profile picture Schedule a Meeting
c a n d l a n d . n e t

Using nant and tallow to create usable WIX component.

Dusty Candland | |

Creating installer for web application can be a pain, but using tallow and an nant script element can make things a lot easier.

Once my nant script was building my solution and web project with all the needed output files I begin to setup automating the build of the WIX component for the web application.

As you know tallow output is not usable as is, some custom handling needs to be done. I've found the script element of nant to work well for this. I've also created a new class library project to keep the script block small, and keep the source in a project. I've included the class library in the solution build. 

Nant target:

<property name="wix.websitefragment.file" value="${build.output.temp}\website-fragment.wsx"/>
<target name="buildwebsitewsx" depends="solution">
<mkdir dir="${build.output.temp}"/>
<exec program="${wix.bin}\tallow.exe" workingdir="${build.output.temp}" output="${wix.websitefragment.file}">
<arg value="-d &quot;${compile.output.webapplication}&quot;"/>
<arg value="-nologo"/>
</exec>
<script language="C#" verbose="true">
<references>
<include name="${compile.output}\InstallHelper.dll"/>
</references>
<imports>
<import namespace="InstallHelper"/>
</imports>
<code>
<![CDATA[
public static void ScriptMain(Project project) {
string file = project.Properties["wix.websitefragment.file"];
new FixTallow(file, "My.ComponentGroup.Name").UpdateFile();
}
]]>
</code>
</script>
</target>
This calls the tallow application on the directory where the web application is built. Then loads and calls the InstallHelper.dll to modify the tallow output into something more usable.

The FixTallow class loads the tallow output into an XML document and takes the following actions; adds a component group, creates guids for the components, renames the directory id's and changes the directoryRef element to point to the INSTALLDIR.

The component group needs to be created so a reference can be added to the main WIX file. This is because tallow creates a component for each directory (or each file) and because I don't want to update the main WIX file every time there is a directory added or removed from the web application. The group name is passed the the constructor of the FixTallow class.

FixTallow also renames the directory id's so that I can reference them if needed from the main WIX file, however if the directory is removed or renamed, the main WIX file will need to be updated. Also, the root DirectoryRef element is changed to reference INSTALLDIR which needs to be set in the main WIX file. This is a good idea as it allows the installation directory to be changed when installing.

FixTallow.cs

    publicclass FixTallow
{
privatereadonlystring _filename;
privatereadonlystring _componentGroupName;
XmlDocument _document = new XmlDocument();
XmlNamespaceManager _manager;
XmlElement _compGroup;

public FixTallow(string filename, string componentGroupName)
{
_filename = filename;
_componentGroupName = componentGroupName;

_manager = new XmlNamespaceManager(_document.NameTable);
_manager.AddNamespace(string.Empty, "http://schemas.microsoft.com/wix/2003/01/wi");
_manager.AddNamespace("ns", "http://schemas.microsoft.com/wix/2003/01/wi");
}

public FixTallow UpdateFile()
{
_document.Load(_filename);

AppendComponentGroupElement();

foreach (XmlElement element in _document.SelectNodes("//ns:Component", _manager))
{
element.Attributes["Guid"].Value = Guid.NewGuid().ToString().ToUpper();
AppendComponentToGroup(element.Attributes["Id"].Value);
}

ChangeDirectoryRefId();

UpdateDirectoryIds();

_document.Save(_filename);

returnthis;
}

privatevoid UpdateDirectoryIds()
{
foreach (XmlElement directory in _document.SelectNodes("//ns:Directory", _manager))
{
string directoryName = (directory.Attributes["LongName"] == null)
?
directory.Attributes["Name"].Value
:
directory.Attributes["LongName"].Value;
directory.Attributes["Id"].Value = directoryName + ".Dir";
}
}

privatevoid ChangeDirectoryRefId()
{
_document.SelectSingleNode("/ns:Wix/ns:Fragment/ns:DirectoryRef/@Id", _manager).Value = "INSTALLDIR";
}

privatevoid AppendComponentToGroup(string componentId)
{
XmlElement componentRef = _document.CreateElement("ComponentRef", _manager.DefaultNamespace);
XmlAttribute componentRefId = _document.CreateAttribute("Id");
componentRefId.Value = componentId;
componentRef.Attributes.Append(componentRefId);
_compGroup.AppendChild(componentRef);
}

privatevoid AppendComponentGroupElement()
{
_compGroup = _document.CreateElement("ComponentGroup", _manager.DefaultNamespace);
XmlAttribute compGroupId = _document.CreateAttribute("Id");
compGroupId.Value = _componentGroupName;
_compGroup.Attributes.Append(compGroupId);
_document.SelectSingleNode("/ns:Wix/ns:Fragment", _manager).AppendChild(_compGroup);
}
}

The main WIX file needs to include a few things for this to work. First, it much have a diretory element with INSTALLDIR as it's Id. Second, a ComponentGroupRef element pointing the component group name specified in the FixTallow constructor needs to be included in the Feature element.

...
<DirectoryId="TARGETDIR"Name="SourceDir">
<DirectoryId="ProgramFilesFolder">
<DirectoryId="INSTALLDIR"Name="myapp"LongName="MyApplicationName">
</Directory>
</Directory>
</Directory>
...
<FeatureId="Complete"Level="1">
<ComponentGroupRefId="My.ComponentGroup.Name"/>
</Feature>
...
Lastly, the tallow output needs to be passed to the candle and light applications when building the msi file with something like this:
<exec
program="${wix.bin}\candle.exe"
workingdir="${build.output.install}"
commandline="Main.wsx website-fragment.wsx"/>
...
<exec
program="${wix.bin}\light.exe"
workingdir="${build.output.install}"
commandline="-out MyApplication.msi Main.wixobj website-fragment.wixobj"/>
There was a lot of setup here, but once completed changes can be made to the web application without modifying the WIX files, resulting in an up to date MSI on each build.

Webmentions

These are webmentions via the IndieWeb and webmention.io. Mention this post from your site: