CustomizableOutDir in TFS 2010

Aaron Hallberg had previously blogged about Preserving Output Directory Structures in Orcas Team Build. The approach he described allows you to work around the problem of having all of your build outputs dumped into a single directory. Unfortunately, things have changed in TFS 2010 and the approach he describes is no longer applicable. There is, however, a way to achieve similar results with a small customization of the default build process template.

The key is the second invocation (the first invokes the “clean” target) of the MSBuild activity which builds a particular solution/project. You’ll need to modify this invocation as follows:

 <mtbwa:MSBuild 
  CommandLineArguments="[String.Format(&quot;/p:SkipInvalidConfigurations=true;
    TeamBuildOutDir=&quot;&quot;{0}&quot;&quot; {1}&quot;, 
      BinariesDirectory, MSBuildArguments)]"
  Configuration="[platformConfiguration.Configuration]" 
  DisplayName="Run MSBuild for Project" 
  GenerateVSPropsFile="[True]" 
  LogFileDropLocation="[logFileDropLocation]" 
  OutDir="[outputDirectory]" 
  Platform="[platformConfiguration.Platform]" 
  Project="[localProject]" 
  RunCodeAnalysis="[RunCodeAnalysis]" 
  TargetsNotLogged="[New String() {
    &quot;GetNativeManifest&quot;, 
    &quot;GetCopyToOutputDirectoryItems&quot;, 
    &quot;GetTargetPath&quot;}]" 
  ToolPlatform="[MSBuildPlatform]" 
  Verbosity="[Verbosity]" />

Note that I’ve changed the CommandLineArguments attribute by setting a TeamBuildOutDir property and removing the OutDir property. This will set a global property named TeamBuildOutDir to the output directory that TFS Build would have redirected all of your build outputs to originally. You can then use that property to selectively copy your build outputs in a manner of your own choosing. For example, if you wanted to simply organize your outputs by solution and project, you could configure each project’s OutputPath as follows:

 <OutputPath Condition="'$(TeamBuildOutDir)'=='' ">
  bin\$(Configuration)\
</OutputPath>
<OutputPath Condition="'$(TeamBuildOutDir)'!='' ">
  $(TeamBuildOutDir)$(SolutionName)\$(ProjectName)\$(Configuration)
</OutputPath>

There are, of course, any number of ways you could implement this depending on your own requirements. For instance, I have a build definition that builds all of the samples for the TFS SDK and copies ZIP files (with the sources) for each solution and VSIX files (for solutions that generate them) to the drop location. If you generate an installer, you may only want to copy that to the drop location.

Note that, if you’re running automated unit tests during your build without a Test Metadata (.vsmdi) file, you’ll also need to customize the SearchPathRoot attribute on the invocation of the MSTest activity to get it to find your unit test assemblies.