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.


Comments (5)

  1. Neil says:

    Just a note for those trying to make this edit in the Workflow Foundations visual editor in VS. If you try and paste in what Jim has above, the &quot; gets incorrectly written to the file as &amp;quot;. It should look like this when you paste it in the edit window:

    String.Format("/p:SkipInvalidConfigurations=true;TeamBuildOutDir=""{0}""; {1}", BinariesDirectory, MSBuildArguments)

    If you are hand-editing the file in something like Notepad then Jim's string is the correct one to use.

  2. Chad Peck says:

    I've followed these instructions and I'm not getting the results that I expected.  I have two Windows Services in my solution that I want output to a _Services subfolder (similar to _PublishedWebsites), but the build isn't creating this folder even though I modified the project files and workflow as required.  The MSBuildTask has BinariesDirectory set as the value of OutDir and it appears that every project is still building to it.  I have about 25 projects in my solution and I only modified the two Windows Service projects.  Do I need to modify all of the projects and set the others to: <OutputPath Condition="'$(TeamBuildOutDir)'=='' ">bin$(Configuration)</OutputPath><OutputPath Condition="'$(TeamBuildOutDir)'!='' ">$(TeamBuildOutDir)</OutputPath> ?  Then do I need to remove the value for OutDir in the MSBuildTask?  This is manual work that will have to be done everytime a project is added to the solution and not all devs would do this, so I'd prefer not to have to update every project, unless it's absolutely necessary.

  3. Kurt Denhoff says:

    I am following your directions to preserve the project output but I am not having success.

    I can see from the log this:

    p:SkipInvalidConfigurations=true;TeamBuildOutDir="D:Builds1KurtDPlayTestWiseBinaries"  /p:OutDir="D:Builds1KurtDPlayTestWiseBinaries\" /

    So it looks like it is setting the TeamBuildOutDir but I notice also the double slash after Binaries. I've looked this over and over but can't seem to find why it's not working.

    Thanks,

    Kurt

  4. denk60 says:

    I am following your directions to preserve the project output but I am not having success.

    I can see from the log this:

    p:SkipInvalidConfigurations=true;TeamBuildOutDir="D:Builds1KurtDPlayTestWiseBinaries"  /p:OutDir="D:Builds1KurtDPlayTestWiseBinaries\" /

    So it looks like it is setting the TeamBuildOutDir but I notice also the double slash after Binaries. I’ve looked this over and over but can’t seem to find why it’s not working.

    Thanks,

    Kurt

  5. JimLamb says:

    I've updated this post so that it should be a little more straightforward to make the necessary changes. One thing that was unclear in the original version was that, in addition to setting the TeamBuildOutDir property, you also need to remove the OutDir property.