<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>The Tranxition Developer Blog &#187; Team Build 2008</title>
	<atom:link href="http://tranxcoder.wordpress.com/tag/team-build-2008/feed/" rel="self" type="application/rss+xml" />
	<link>http://tranxcoder.wordpress.com</link>
	<description>Random musings from the developers at Tranxition</description>
	<lastBuildDate>Tue, 24 Mar 2009 18:14:16 +0000</lastBuildDate>
	<generator>http://wordpress.com/</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<cloud domain='tranxcoder.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://www.gravatar.com/blavatar/a141c5ce8e6e7831514f7c306752642f?s=96&#038;d=http://s.wordpress.com/i/buttonw-com.png</url>
		<title>The Tranxition Developer Blog &#187; Team Build 2008</title>
		<link>http://tranxcoder.wordpress.com</link>
	</image>
			<item>
		<title>A custom task for building WiX under MSBuild (TFS)</title>
		<link>http://tranxcoder.wordpress.com/2008/07/03/a-custom-task-for-building-wix-under-msbuild-tfs/</link>
		<comments>http://tranxcoder.wordpress.com/2008/07/03/a-custom-task-for-building-wix-under-msbuild-tfs/#comments</comments>
		<pubDate>Thu, 03 Jul 2008 01:39:45 +0000</pubDate>
		<dc:creator>hempelcx</dc:creator>
				<category><![CDATA[Tools]]></category>
		<category><![CDATA[Team Build 2008]]></category>
		<category><![CDATA[TFS]]></category>
		<category><![CDATA[WiX]]></category>

		<guid isPermaLink="false">http://tranxcoder.wordpress.com/2008/07/03/a-custom-task-for-building-wix-under-msbuild-tfs/</guid>
		<description><![CDATA[My name is Shawn (Hempel) and this is my first submission to the Tranxcoder blog, which is a little ironic since I think it was my idea in the first place. Nonetheless, at least now I am officially &#8220;on the board&#8221; (and the team can stop razzing me about not posting.)
It seems I&#8217;ll be continuing [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tranxcoder.wordpress.com&blog=3688975&post=45&subd=tranxcoder&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>My name is Shawn (Hempel) and this is my first submission to the Tranxcoder blog, which is a little ironic since I think it was my idea in the first place. Nonetheless, at least now I am officially &#8220;on the board&#8221; (and the team can stop razzing me about not posting.)</p>
<p>It seems I&#8217;ll be continuing on a running theme of writing about making Team Foundation Server work for us in new and scary ways. If you&#8217;re wondering why so much of our blog has centered on that topic, I think it&#8217;s two parts: 1) in many ways TFS customization is neither easy or intuitive so as we struggle to make things work, we want to share that with the development community; and 2) since our build process has little to do with the logic in our products, it&#8217;s easy to put this stuff out here without worrying about giving away intellectual property. That said, there will probably be more non-build related content as time goes on &#8211; we&#8217;ll just change variable names to protect the innocent. Alright, on to the good stuff&#8230;</p>
<p>As previously <a title="WiX, MSBuild, and Build Verification, Part 1" href="http://tranxcoder.wordpress.com/2008/06/27/wix-msbuild-and-build-verification-part-1/">mentioned by Richard</a>, we use the <a title="Windows Installer XML (WiX) toolset" href="http://wix.sourceforge.net/" target="_blank">WiX toolset</a> to generate Windows Installer binaries (MSI/MSM) for our applications. Personally, I find the recent builds of <a title="WiX - Votive Project" href="http://wix.sourceforge.net/votive.html" target="_blank">Votive</a> (WiX&#8217;s Visual Studio integration package) to be quite helpful and easy to work with. One of the things it does very well is enable a simple F5 installer build. What it does <em>not</em> do, however, is help you in any way once you decide to graduate from building on the desktop to incorporating an installer build in your TFS processes. Specifically, there will almost certainly be some trouble with the relative and/or absolute paths used in your WiX source files to locate the build outputs to package. Read on to find out how we first solved that problem in a way that made it hard to sleep at night, and subsequently replaced it with a robust, maintainable solution.</p>
<p><span id="more-45"></span>Like all good WiX developers, we attempt to abstract away the details about file paths from the actual &lt;File&gt; directives as much as possible. For instance, in <span style="font-size:x-small;font-family:consolas;">Files.wxs</span> we defined the following component:
</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">18</span> <span style="color:blue;">&lt;</span><span style="color:#a31515;">Component</span><span style="color:blue;"> </span><span style="color:red;">Id</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">SKUApp</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Guid</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">91062BB2-7010-4960-949E-E9452890645F</span>&#8220;<span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">19</span>&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">File</span><span style="color:blue;"> </span><span style="color:red;">Id</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">SKUAppExe</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">KeyPath</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">yes</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(var.SKUName).exe</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Source</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(var.SKUBinPath)\</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Vital</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">yes</span>&#8221; /<span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">22</span>&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">File</span><span style="color:blue;"> </span><span style="color:red;">Id</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">SKUAppExeConfig</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">KeyPath</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">no</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(var.SKUName).exe.config</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Source</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(var.SKUBinPath)\</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">Vital</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">yes</span>&#8220;<span style="color:blue;"> /&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">29</span> <span style="color:blue;">&lt;/</span><span style="color:#a31515;">Component</span><span style="color:blue;">&gt;</span></p>
</div>
<p>You can see that rather than providing an actual path (or in this case, even an actual file name) it specifies that a variable $(var.SKUBinPath) will contain the path to the source file and that $(var.SKUName) will contain the name portion of the file path, while the extension will be specific to each file Id. Ultimately, this component will locate and package the <span style="font-size:x-small;font-family:consol;">$(OutDir)/App.exe</span> and <span style="font-size:x-small;font-family:consolas;">$(OutDir)/App.exe.config</span> files which are generated as output from a vcbuild project.</p>
<p>The variables referenced are defined in a separate file (cleverly) named <span style="font-size:x-small;font-family:con;">Variables.wxi</span> (the .wxi extension suggests that it&#8217;s an <a title="Preprocessor" href="http://wix.sourceforge.net/manual-wix2/preprocessor.htm" target="_blank"><em>include</em> file</a> rather than normal WiX source). Within the include file are some preprocessor variable definitions as follows:</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">2</span> <span style="color:blue;">&lt;</span><span style="color:#a31515;">Include</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">3</span>&nbsp;&nbsp; <span style="color:blue;">&lt;?</span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">SKUName = &#8220;App&#8221; </span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">4</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">5</span>&nbsp;&nbsp; <span style="color:blue;">&lt;?</span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">SourceLocation = &#8220;..&#8221;</span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">6</span>&nbsp;&nbsp; <span style="color:blue;">&lt;?</span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">CompilationMode=Release</span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">7</span>&nbsp;&nbsp; <span style="color:blue;">&lt;?</span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">SKUBinPath=$(var.SourceLocation)\$(var.CompilationMode)</span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">8</span> <span style="color:blue;">&lt;/</span><span style="color:#a31515;">Include</span><span style="color:blue;">&gt;</span></p>
</div>
<p>When the WiX project is built the preprocessor will evaluate those definitions and generate $(var.XXX) variables which can be used throughout any source which pulls in this include file (such as the SKUApp component above.) Notice that the value for $(var.SKUBinPath) is built by concatenating $(var.SourceLocation) and $(var.CompilationMode); but look at the value provided for the SourceLocation variable &#8211; it&#8217;s a relative path meaning &#8220;use the current directory&#8217;s parent&#8221;. In this case, that path will be correct as long as the project is built under Visual Studio as part of its containing solution, but something happens when you try to build it under TFS&#8230;</p>
<p>One of the first things I discovered about Team Build is that all output from a solution is placed in a single drop location called $(OutDir). Without any outside influence, that location will be something like &#8220;<span style="font-size:x-small;font-family:consola;">$(BuildDirectoryPath)\$(TeamProject)\Binaries\$(Platform)\&#8221;</span>. In other words, the <strong>location</strong> of the binaries produced by your builds from Team Build will likely <strong><em>not</em></strong> mirror the location of those same binaries when built from Visual Studio on a developer&#8217;s machine. So if you want your developers to be able to build and validate the installer simply by hitting F5, and have that same installer project build automatically on the build machine, then the problem is obvious &#8211; we have to do something about the SourceLocation path to make it correct for both.</p>
<p>The first approach we used was to take advantage of a WiX preprocessor feature called <a title="Preprocessor" href="http://wix.sourceforge.net/manual-wix2/preprocessor.htm" target="_blank">Conditional Statements</a>, which are vary similar to preprocessor conditionals in C++ and other languages. We used the value of a particular environment variable as sufficient evidence to determine whether the build is running on the build system or on a developer&#8217;s machine. And it looked something like this:</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">1</span> <span style="color:blue;">&lt;?</span><span style="color:#a31515;">if</span><span style="color:blue;"> </span><span style="color:gray;">$(sys.SOURCEFILEDIR) = &#8220;C:\Build Directories\App\Project\Sources\Main\Source\Setup\&#8221; </span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">2</span>&nbsp;&nbsp; <span style="color:blue;">&lt;?</span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">SourceLocation = &#8220;C:\Build Directories\App\Project\Binaries\Mixed Platforms&#8221;</span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">3</span> <span style="color:blue;">&lt;?</span><span style="color:#a31515;">else</span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">4</span>&nbsp;&nbsp; <span style="color:blue;">&lt;?</span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">SourceLocation = &#8220;..&#8221;</span><span style="color:blue;">?&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">5</span> <span style="color:blue;">&lt;?</span><span style="color:#a31515;">endif</span><span style="color:blue;">?&gt;</span></p>
</div>
<p>I don&#8217;t know about you, but every time I see code like that all I can think of is &#8220;somewhere in the world, a kitten just died.&#8221; Maybe that&#8217;s a bit of an overreaction, but it does evoke a visceral response in that it just screams of future maintenance nightmares. For instance, what if we want to build this project on multiple build machines? What if the path on this build machine changes? What if the <span style="font-size:x-small;font-family:consolas;">SOURCEFILEDIR</span> variable goes away or its meaning changes? Etc, etc..</p>
<p>In my opinion, any approach which relies on environmental evidence to determine which hard-coded path to use is sub-optimal. Not so much because of the evidence, but because of the hard-coded paths. What we really need is a way to allow those paths to be provided by the build process rather than being specified in the checked-in source. While there are myriad options for achieving that goal, the quickest and most flexible seemed to be creating a custom MSBuild task for replacing variable definitions in a WiX file, which is exactly what the remainder of this post describes.</p>
<p>The custom task is called WixVarSubstitution and is intended to be a general solution for replacing the value of any variable defined in any WiX source file. For a concrete example consider the SourceLocation variable from above. To change the value of that variable within the build script on your build machine you could create a target like this:</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 1</span> <span style="color:blue;">&lt;</span><span style="color:#a31515;">PropertyGroup</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 2</span>&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">_SourceFilePath</span><span style="color:blue;">&gt;</span>$(SolutionRoot)\Main\Source\Setup\Variables.wxi<span style="color:blue;">&lt;/</span><span style="color:#a31515;">_SourceFilePath</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 3</span>&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">_VariableDefinitions</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 4</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">VariableDefinition</span><span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">SourceLocation</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">NewValue</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(BinariesRoot)\$(Platform)</span>&#8220;<span style="color:blue;"> /&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 5</span>&nbsp;&nbsp; <span style="color:blue;">&lt;/</span><span style="color:#a31515;">_VariableDefinitions</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 6</span> <span style="color:blue;">&lt;/</span><span style="color:#a31515;">PropertyGroup</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 7</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 8</span> <span style="color:blue;">&lt;</span><span style="color:#a31515;">Target</span><span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">AdjustInstallerSourceBinaryLocation</span>&#8220;<span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 9</span>&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">WixVarSubstitution</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">10</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:blue;"></span><span style="color:red;">SourceFile</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(_SourceFilePath)</span>&#8220;</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">11</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:blue;"></span><span style="color:red;">VariableDefinitions</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">$(_VariableDefinitions)</span>&#8220;<span style="color:blue;"> /&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">12</span> <span style="color:blue;">&lt;/</span><span style="color:#a31515;">Target</span><span style="color:blue;">&gt;</span></p>
</div>
<p>When called, that target will open the file <span style="font-size:x-small;font-family:consol;">Variables.wxi</span>, locate and replace the value in <span style="color:blue;">&lt;? </span><span style="color:#a31515;">define</span><span style="color:blue;"> </span><span style="color:gray;">SourceLocation = &#8220;..&#8221; </span><span style="color:blue;">?&gt;</span> with a new value determined from <span style="font-size:x-small;font-family:consol;">$(BinariesRoot)\$(Platform)</span> and save the change. Then when the build for the Setup project runs, it will have the correct location of the output binaries to package up in the installer.</p>
<p>A key assumption made by this custom task is that variables are specified using the &lt;?define name=&#8221;value&#8221;?&gt; syntax known as an XML <a title="Extensible Markup Language (XML) 1.0 (Fourth Edition)" href="http://www.w3.org/TR/REC-xml/#sec-pi" target="_blank">processing instruction</a>. If, for instance, WiX changes the <a title="www.w3.org" href="http://www.w3.org/TR/REC-xml/#NT-PITarget" target="_blank">PITarget</a> name from &#8220;define&#8221; to something else, the custom task will have to be modified. Further, if WiX enables a method of defining variables outside of preprocessing instructions then that will need to be taken into account as well. However, for our purposes and given what I know about WiX today, I believe this task covers the typical case &#8211; certainly it has handled our primary need. So on to the task itself.</p>
<p>The details of creating a custom MSBuild task are documented in numerous places all over the &#8216;Net, including blogs and MSDN. One thing that wasn&#8217;t immediately clear to me, though, was what to do if you want to pass a structure or, worse, an array of structures to a custom task. Initially I thought I could just use an <a title="ITaskItem Interface" href="http://msdn.microsoft.com/en-us/library/microsoft.build.framework.itaskitem.aspx" target="_blank">ITaskItem</a> array, but quickly realized ITaskItem assumes a correlation to a file on the file system. While it&#8217;s possible to tweak that model and use it for other things, that approach did not seem clean. Eventually I discovered that you can easily generate XML properties directly within an MSBuild script. To see the creation of a sample XML property, look at the <span style="color:blue;">&lt;</span><span style="color:#a31515;">_VariableDefinitions</span><span style="color:blue;">&gt;</span> declaration above. While that shows an XML document with only a single node, it can easily be expanded to multiple:</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">3</span> <span style="color:blue;">&lt;</span><span style="color:#a31515;">_VarDefs</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">4</span>&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">Root</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">5</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">VariableDefinition</span><span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">Var1</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">NewValue</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">Val1</span>&#8220;<span style="color:blue;"> /&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">6</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">VariableDefinition</span><span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">Var2</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">NewValue</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">Val2</span>&#8220;<span style="color:blue;"> /&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">7</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:blue;">&lt;</span><span style="color:#a31515;">VariableDefinition</span><span style="color:blue;"> </span><span style="color:red;">Name</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">Var3</span>&#8220;<span style="color:blue;"> </span><span style="color:red;">NewValue</span><span style="color:blue;">=</span>&#8220;<span style="color:blue;">Val3</span>&#8220;<span style="color:blue;"> /&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">8</span>&nbsp;&nbsp; <span style="color:blue;">&lt;/</span><span style="color:#a31515;">Root</span><span style="color:blue;">&gt;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">9</span> <span style="color:blue;">&lt;/</span><span style="color:#a31515;">_VarDefs</span><span style="color:blue;">&gt;</span></p>
</div>
<p>The name of the property itself (in this case <span style="color:#a31515;">_VarDefs</span>) is immaterial. Also immaterial is the name of the <span style="color:#a31515;">Root</span> element you create when you want to specify more than one <span style="color:#a31515;">VariableDefinition</span> (necessary because XML documents <a title="www.w3.org" href="http://www.w3.org/TR/REC-xml/#NT-document" target="_blank">may only have one root</a>). In fact all that does matter is that you name the material elements &#8220;<strong>VariableDefinition</strong>&#8221; and provide values for the attributes &#8220;<strong>Name</strong>&#8221; (the name of the variable to locate) and &#8220;<strong>NewValue</strong>&#8221; (the value to provide in place of the existing value.) There is no practical limit to the number of variable definitions provided.</p>
<p>That XML document is passed as a string to the custom task in a property.</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">55</span> <span style="color:navy;">public</span> <span style="color:navy;">string</span> <span style="color:maroon;">VariableDefinitions</span> { <span style="color:navy;">get</span>; <span style="color:navy;">set</span>; }</p>
</div>
<p>Upon calling <a title="MSBuild.Execute Method" href="http://msdn.microsoft.com/en-us/library/microsoft.build.tasks.msbuild.execute.aspx" target="_blank">Execute()</a> the XML is parsed and turned into a dictionary of Key/Value pairs where the variable names are the keys.</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">79</span> <span style="color:green;">// Parse the provided XML string</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">80</span> <span style="color:navy;">var</span> <span style="color:maroon;">root</span> = <span style="color:#a65300;">XElement</span>.<span style="color:maroon;">Parse</span>( <span style="color:maroon;">VariableDefinitions</span>, <span style="color:#2b91af;">LoadOptions</span>.<span style="color:maroon;">SetBaseUri</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">81</span> <span style="color:navy;">var</span> <span style="color:maroon;">ns</span> = <span style="color:maroon;">root</span>.<span style="color:maroon;">GetDefaultNamespace</span>();</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">82</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">83</span> <span style="color:green;">// Build up a dictionary of variables and replacement values</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">84</span> <span style="color:#a65300;">Array</span>.<span style="color:maroon;">ForEach</span>( <span style="color:maroon;">root</span>.<span style="color:maroon;">Elements</span>( <span style="color:maroon;">ns</span> + <span style="background:#ffffe6;">&#8220;VariableDefinition&#8221;</span> ).<span style="color:maroon;">ToArray</span>(), <span style="color:maroon;">n</span> =&gt;</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">85</span> {</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">86</span>&nbsp;&nbsp; <span style="color:maroon;">_substitutions</span>.<span style="color:maroon;">Add</span>( <span style="color:maroon;">n</span>.<span style="color:maroon;">Attribute</span>( <span style="background:#ffffe6;">&#8220;Name&#8221;</span> ).<span style="color:maroon;">Value</span>, <span style="color:maroon;">n</span>.<span style="color:maroon;">Attribute</span>( <span style="background:#ffffe6;">&#8220;NewValue&#8221;</span> ).<span style="color:maroon;">Value</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">87</span> } );</p>
</div>
<p>In order to retrieve elements by name, you must specify the document namespace if one is used &#8211; and MSBuild will always include a namespace in its XML properties (specifically, &#8220;<span style="font-size:x-small;font-family:consol;">http://schemas.microsoft.com/developer/msbuild/2003</span>&#8220;). However, the task doesn&#8217;t need to know anything about the namespace, so it is sufficient to request it from the parsed root XElement and use that when querying for the variable definition elements.</p>
<p>Note, don&#8217;t let the lambda expression fool you into thinking there&#8217;s anything complicated going on there &#8211; it&#8217;s simply a for each over an array of XElement instances. Each instance is used to add a new Key/Value pair to the _substitutions dictionary defined as:</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">28</span> <span style="color:navy;">private</span> <span style="color:navy;">var</span> <span style="color:maroon;">_substitutions</span> = <span style="color:navy;">new</span> <span style="color:#a65300;">Dictionary</span>&lt;<span style="color:navy;">string</span>, <span style="color:navy;">string</span>&gt;( <span style="color:#a65300;">StringComparer</span>.<span style="color:maroon;">OrdinalIgnoreCase</span> );</p>
</div>
<p>Additionally, I have defined a regular expression as a string constant:</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;">22</span> <span style="color:green;">// This pattern should match the way WiX variables are defined, e.g. &#8216;&lt;?define SourceLocation = &#8220;..&#8221; ?&gt;&#8217;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">23</span> <span style="color:green;">// except it only matches on the Data portion of the PI: &#8216;SourceLocation = &#8220;..&#8221;&#8216;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">24</span> <span style="color:navy;">private</span> <span style="color:navy;">const</span> <span style="color:navy;">string</span> <span style="color:maroon;">PIDataRegExPattern</span> = <span style="background:#ffffe6;">&#8220;^{0}\\s*=\\s*\&#8221;(?&lt;value&gt;.*)\&#8221;"</span>;</p>
</div>
<p>Following the creation of a dictionary is a fun bit of LINQ to XML combined with regular expressions and a little composite formatting. If you&#8217;re not familiar with all of these, the comments should be sufficient to give you an idea of what&#8217;s happening (note that it loads the file specified in the string property <span style="color:maroon;">SourceFile</span>, which was seen in the usage sample earlier):</p>
<div style="font-size:10pt;background:#f8f8f8;color:black;font-family:consolas;">
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 92</span> <span style="color:green;">// Aggregate _substitutions keys and produce a string like &#8220;(key1)|(key2)|(key3)&#8230;&#8221;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 93</span> <span style="color:navy;">var</span> <span style="color:maroon;">varNames</span> = <span style="background:#ffffe6;">&#8220;(&#8220;</span> + ( ( <span style="color:maroon;">_substitutions</span>.<span style="color:maroon;">Keys</span>.<span style="color:maroon;">Aggregate</span>( ( <span style="color:maroon;">leftKey</span>, <span style="color:maroon;">rightKey</span> ) =&gt; <span style="background:#ffffe6;">&#8220;(&#8220;</span> + <span style="color:maroon;">leftKey</span> + <span style="background:#ffffe6;">&#8220;)|(&#8220;</span> + <span style="color:maroon;">rightKey</span> ) + <span style="background:#ffffe6;">&#8220;)&#8221;</span> ).<span style="color:maroon;">TrimStart</span>( <span style="background:#ffffe6;">&#8216;(&#8216;</span> ) );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 94</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 95</span> <span style="color:green;">// Because of the way PIDataRegExPattern is laid out, just stuff the aggregated string into the pattern to generate a Regex<br /></span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 96</span> <span style="color:navy;">var</span> <span style="color:maroon;">varNamesRegEx</span> = <span style="color:navy;">new</span> <span style="color:#a65300;">Regex</span>( <span style="color:navy;">string</span>.<span style="color:maroon;">Format</span>( <span style="color:#a65300;">CultureInfo</span>.<span style="color:maroon;">InvariantCulture</span>, <span style="color:maroon;">PIDataRegExPattern</span>, <span style="color:maroon;">varNames</span> ), <span style="color:#2b91af;">RegexOptions</span>.<span style="color:maroon;">IgnoreCase</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 97</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 98</span> <span style="color:green;">// This query loads the SourceFile as an XDocument and retrieves all XProcessingInstruction nodes</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;"> 99</span> <span style="color:green;">// whose Target is &#8216;define&#8217; and whose name matches any of the _substitutions keys</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">100</span> <span style="color:navy;">var</span> <span style="color:maroon;">replaceNodesQuery</span> = <span style="color:navy;">from</span> <span style="color:maroon;">e</span> <span style="color:navy;">in</span> <span style="color:#a65300;">XDocument</span>.<span style="color:maroon;">Load</span>( <span style="color:maroon;">SourceFile</span>, <span style="color:#2b91af;">LoadOptions</span>.<span style="color:maroon;">PreserveWhitespace</span> ).<span style="color:maroon;">DescendantNodes</span>()</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">101</span>&nbsp;&nbsp; <span style="color:navy;">let</span> <span style="color:maroon;">n</span> = <span style="color:maroon;">e</span> <span style="color:navy;">as</span> <span style="color:#a65300;">XProcessingInstruction</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">102</span>&nbsp;&nbsp; <span style="color:navy;">where</span> ( <span style="color:maroon;">e</span>.<span style="color:maroon;">NodeType</span> == <span style="color:#2b91af;">XmlNodeType</span>.<span style="color:maroon;">ProcessingInstruction</span> )</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">103</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; <span style="color:maroon;">n</span> != <span style="color:navy;">null</span> <span style="color:green;">// Not sure if I really need this</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">104</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; <span style="color:maroon;">n</span>.<span style="color:maroon;">Target</span> == <span style="background:#ffffe6;">&#8220;define&#8221;</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">105</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; <span style="color:maroon;">varNamesRegEx</span>.<span style="color:maroon;">IsMatch</span>( <span style="color:maroon;">n</span>.<span style="color:maroon;">Data</span> )</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">106</span>&nbsp;&nbsp; <span style="color:navy;">select</span> <span style="color:maroon;">n</span>;</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">107</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">108</span> <span style="color:green;">// Force deferred execution to complete so we can modify the XDocument</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">109</span> <span style="color:navy;">var</span> <span style="color:maroon;">replaceNodes</span> = <span style="color:maroon;">replaceNodesQuery</span>.<span style="color:maroon;">ToArray</span>();</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">110</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">126</span> <span style="color:green;">// Loop through each substitution with a nested loop against replaceable nodes and do a regex replacement</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">127</span> <span style="color:green;">// This is safe because the pattern match will only succeed when substition.Key matches the variable name</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">128</span> <span style="color:green;">// Note: It just assigns the Data property back to itself when there&#8217;s no match</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">129</span> <span style="color:navy;">foreach</span> ( <span style="color:navy;">var</span> <span style="color:maroon;">substitution</span> <span style="color:navy;">in</span> <span style="color:maroon;">_substitutions</span> )</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">130</span> {</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">131</span>&nbsp;&nbsp; <span style="color:maroon;">Log</span>.<span style="color:maroon;">LogMessage</span>( <span style="color:#2b91af;">MessageImportance</span>.<span style="color:maroon;">Low</span>, <span style="background:#ffffe6;">&#8220;&gt; Attempting to replace variable &#8216;{0}&#8217; with value &#8216;{1}&#8217; in SourceFile &#8216;{2}&#8217;&#8221;</span>, <span style="color:maroon;">substitution</span>.<span style="color:maroon;">Key</span>, <span style="color:maroon;">substitution</span>.<span style="color:maroon;">Value</span>, <span style="color:maroon;">SourceFile</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">132</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">133</span>&nbsp;&nbsp; <span style="color:navy;">foreach</span> ( <span style="color:navy;">var</span> <span style="color:maroon;">replaceNode</span> <span style="color:navy;">in</span> <span style="color:maroon;">replaceNodes</span> )</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">134</span>&nbsp;&nbsp; {</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">135</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:navy;">var</span> <span style="color:maroon;">pattern</span> = <span style="color:navy;">string</span>.<span style="color:maroon;">Format</span>( <span style="color:#a65300;">CultureInfo</span>.<span style="color:maroon;">InvariantCulture</span>, <span style="color:maroon;">PIDataRegExPattern</span>, <span style="color:maroon;">substitution</span>.<span style="color:maroon;">Key</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">136</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:navy;">var</span> <span style="color:maroon;">replacement</span> = <span style="color:navy;">string</span>.<span style="color:maroon;">Format</span>( <span style="color:#a65300;">CultureInfo</span>.<span style="color:maroon;">InvariantCulture</span>, <span style="background:#ffffe6;">&#8220;{0} = \&#8221;{1}\&#8221;"</span>, <span style="color:maroon;">substitution</span>.<span style="color:maroon;">Key</span>, <span style="color:maroon;">substitution</span>.<span style="color:maroon;">Value</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">137</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">138</span>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color:maroon;">replaceNode</span>.<span style="color:maroon;">Data</span> = <span style="color:#a65300;">Regex</span>.<span style="color:maroon;">Replace</span>( <span style="color:maroon;">replaceNode</span>.<span style="color:maroon;">Data</span>, <span style="color:maroon;">pattern</span>, <span style="color:maroon;">replacement</span>, <span style="color:#2b91af;">RegexOptions</span>.<span style="color:maroon;">IgnoreCase</span> );</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">139</span>&nbsp;&nbsp; }</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">140</span> }</p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">141</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">142</span> <span style="color:green;">// Write the changes back out to the input file</span></p>
<p style="margin:0;"><span style="background:gray 0 0;color:white;">143</span> <span style="color:maroon;">replaceNodes</span>[ <span style="background:#e6ffff;">0</span> ].<span style="color:maroon;">Document</span>.<span style="color:maroon;">Save</span>( <span style="color:maroon;">SourceFile</span>, <span style="color:#2b91af;">SaveOptions</span>.<span style="color:maroon;">DisableFormatting</span> );</p>
</div>
<p>Essentially we&#8217;re processing an XML file (the WiX source), performing a lookup against a dictionary of keys, replacing matched values, and rewriting the file all in less than 20 lines. In the full source code there is some additional error checking and logging in place, but the meat is all here.</p>
<p>In a future post I (or someone else) will discuss how we go about getting our custom tasks into the build system and make use of them in our scripts. However, most of that is well documented around the &#8216;Net. In the meantime, I&#8217;m providing the source code for this post in the form of a <a title="WixVarSubstitution.cs" href="http://www.hempel.cx/Misc/WixVarSubstitution.cs" target="_self">WixVarSubstitution.cs</a> file, which you are welcome to use in your own build environments and/or modify to suit your needs. The only thing required to build it beyond a standard C# DLL project is references to System.XML.Linq, Microsoft.Build.Framework and Microsoft.Build.Utilities.v3.5.</p>
<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/tranxcoder.wordpress.com/45/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/tranxcoder.wordpress.com/45/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tranxcoder.wordpress.com/45/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tranxcoder.wordpress.com/45/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tranxcoder.wordpress.com/45/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tranxcoder.wordpress.com/45/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tranxcoder.wordpress.com/45/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tranxcoder.wordpress.com/45/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tranxcoder.wordpress.com/45/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tranxcoder.wordpress.com/45/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tranxcoder.wordpress.com/45/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tranxcoder.wordpress.com/45/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tranxcoder.wordpress.com&blog=3688975&post=45&subd=tranxcoder&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://tranxcoder.wordpress.com/2008/07/03/a-custom-task-for-building-wix-under-msbuild-tfs/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
	
		<media:content url="" medium="image">
			<media:title type="html">hemp</media:title>
		</media:content>
	</item>
	</channel>
</rss>