The Ramblings of Two Microsoft .NET Developers, TFS, and Visual Studio ALM Guys --- "Yes, we are both named Ed."

Customizable Output Directories for TFS 2010 Build



By default, when you are setting up a new build definition in TFS 2010 that uses multiple Visual Studio solutions and projects, you end up with a drop folder with all of the compiled assemblies dropped in the root.  One notable exception to this is for web application projects where you will end up with a subfolder underneath the _PublishedWebsites folder for each of the web application projects.  You may not want everything to be in the root and instead want it organized.  There were several methods for how to do this using TFS 2005 or TFS 2008 but let me show you a way to do it for a TFS 2010 Build.

Create Workflow Parameter

Open up your build process template file and go to your “Arguments” tab (available in the lower-left hand corner of the workflow designer) to add a new workflow parameter.  My suggestion would be to use a new Boolean argument named “CustomizableOutputDirectory” as shown below.

image

Next, open up the Process Parameter Metadata Editor by clicking on the editor button for the Metadata workflow argument that is already defined.  Next, give your new process parameter some extra information so that someone editing the build definition will understand what this parameter is for.  You’ll notice that I’m also putting this parameter in the Advanced category since I don’t really need to create another category for only one new parameter.  Feel free to create a new grouping if you have more custom parameters in your build process template.

SNAGHTML627addb

Once you finish these steps and check-in the file to version control, you’ll see your new parameter in the build definition editor as shown below.

clip_image002

Add Functionality to the Workflow

The next step is to add in the appropriate functionality for the customizable output directory.  What we will end up doing is adding an alternate path for when MSBuild is called to compile the Visual Studio solution or project each time.  I want to leave the default functionality though if the process parameter we created above is set to False.  So, the first step is to find the default functionality for compilation.  You can find it by navigating deep into the workflow and finding the “Compile the Project” sequence activity that contains the MSBuild activity that is used for compilation.  You can find it by navigating through these activities:

  • Sequence
  • Run on Agent (AgentScope)
  • Type Compile, Test, and Associate Changesets and Work Items (TryCatch)
  • Sequence
  • Compile, Test, and Associate Changesets and Work Items (Parallel)
  • Try Compile and Test (TryCatch)
  • Compile and Test (Sequence)
  • For Each Configuration in BuildSettings.PlatformConfigurations (ForEach<T>)
  • Compile and Test for Configuration (Sequence)
  • If BuildSettings.HasProjectsToBuild (If)
  • For Each Project in BuildSettings.ProjectsToBuild (ForEach<T>)
  • Try to Compile the Project (TryCatch)
  • Compile the Project (Sequence)

image

The next step I would take is to drag and drop a new If activity and place the existing MSBuild activity in the Else container.  This allows for the default functionality to still continue if the value of the custom process parameter is False.  Set the Condition parameter for the If activity to the name of the custom process parameter that was defined earlier as shown below.

image

You will notice in the MSBuild activity that exists for default functionality, the CommandLineArguments workflow activity argument is set to the following.

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

To implement our custom output directory functionality, we are essentially going to modify this one activity parameter to pass in a new value.  This will be the new expression that we will use for the MSBuild activity parameter:

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

The easiest way to do this would be to copy the existing MSBuild activity and paste it in the Then container for our If activity.  You can then set the CommandLineArguments parameter for the copied activity to the new value that defines the new MSBuild parameter.

image

Extra Credit Aside:  Anyone know why we used outputDirectory instead of BinariesDirectory?

Opt-In for Visual Studio Projects

Once you have done all of the steps, you’ll notice that it didn’t actually change anything Smile.  For each of the Visual Studio projects that you want to use the new functionality, you will need to “opt-in” by editing the .csproj or .vbproj Visual Studio project files and adding the new MSBuild property group shown below.

<!-- Customizable Output Directory Opt-In for TFS Build (non-web application projects) -->
<
PropertyGroup Condition="$(TeamBuildOutDir) != '' "
>
  <
OutputPath>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)</OutputPath
>
</
PropertyGroup
>

The change does break the _PublishedWebsites functionality for any web application projects though and you will need to use a different opt-in below for every web application project that is included in the build.

<!-- Customizable Output Directory Opt-In for TFS Build (web application projects) -->
<
PropertyGroup Condition="$(TeamBuildOutDir) != '' "
>
  <
OutDir>$(TeamBuildOutDir)</OutDir
>
</
PropertyGroup
>

There you go… Check those project file changes into version control and you can now queue a new build and the outputs for each of the projects that are opting-in to this new functionality are created in subfolders in the drop folder.  It was also still continue to put web application projects into the _PublishedWebsites folders.

 

Ed Blankenship



Monday, January 31, 2011 2:44:39 PM (Eastern Standard Time, UTC-05:00)
Great post! I have Silverlight projects that that have two projects outputting the same filename (one compiled for Silverlight, the other for .NET) and had to code this for myself. This makes for a really nice concise guide.

Jason
Friday, April 29, 2011 3:47:53 PM (Eastern Daylight Time, UTC-04:00)
Thanks for this post, Ed. One correction, however...

When you create the new MS Build activity, you must clear the OutDir parameter so it doesn't contain the default 'outputDirectory' value. If you don't do that, the folders don't get created for the non-web projects. It only took me 3 hours to figure that out :-S
Friday, April 29, 2011 3:48:39 PM (Eastern Daylight Time, UTC-04:00)
Thanks for this post, Ed. One correction, however...

When you create the new MS Build activity, you must clear the OutDir parameter so it doesn't contain the default 'outputDirectory' value. If you don't do that, the folders don't get created for the non-web projects. It only took me 3 hours to figure that out :-S
Friday, April 29, 2011 5:20:54 PM (Eastern Daylight Time, UTC-04:00)
Thanks for the contribution! I recently just ran into this problem earlier this week. I also ended up wasting a ton of time trying to figure it out! :)
Wednesday, May 11, 2011 4:23:33 PM (Eastern Daylight Time, UTC-04:00)
I have the exact same problem - how do I clear the OutDir parameter?
Friday, July 22, 2011 3:36:02 PM (Eastern Daylight Time, UTC-04:00)
Is there a way to supress the web projects BIN directory being created in the route of the TeamBuildOutDir ??
JIm Groves
Thursday, July 28, 2011 5:48:02 PM (Eastern Daylight Time, UTC-04:00)
Hi I'm looking to fix this issue in TFS2005 and couldn't find the instructions for the same. Could you please guide me to a link?
Wednesday, October 05, 2011 10:34:50 AM (Eastern Daylight Time, UTC-04:00)
Cannot get the web site fix (_PublishedWebsites) to work alongside the fix of output to dedicated folders per project. Any help is appreciated...
Kenander
Wednesday, February 01, 2012 7:16:09 PM (Eastern Standard Time, UTC-05:00)
Hey Ed,
Where/how do you define the 'outputDirectory' argument that is passed into the MSBuild arguments? Is it a new argument that I create and add to the template? Or is it defined somewhere else but I just don't access to it for some reason?
Wednesday, February 01, 2012 8:26:52 PM (Eastern Standard Time, UTC-05:00)

Where/how do you define the 'outputDirectory' argument that is passed into the MSBuild arguments? Is it a new argument that I create and add to the template?

That's a great question Joseph. It's a defined workflow variable that has a scope of the Sequence activity with the label of "Compile and Test for Configuration." It is initialized in the very first sequence labeled "Initial Variables."

You can see it by going to the Variables tab at the bottom of the workflow designer.
Wednesday, February 01, 2012 8:51:35 PM (Eastern Standard Time, UTC-05:00)
Thanks for the response. I'm still not seeing it, though. I only see the Workspace Name, Sources Directory, Binaries Directory, and TestResults Directory being initialized in the 'Initialize Variables'sequence. Am I missing something? I don't see it anywhere on the arguments list below either, regardless of which sequence I have selected (I tried a few to make sure I was in the right scope).
Wednesday, February 01, 2012 9:32:25 PM (Eastern Standard Time, UTC-05:00)
I think I've found the source of my confusion. I hadn't realized the 'Run MSBuild for Project' activity is executed twice in this template, I was trying to implement these changes in the first instance, where 'outputDirectory' isn't in scope yet.
Wednesday, February 01, 2012 9:34:58 PM (Eastern Standard Time, UTC-05:00)
There you go... that would do it. Glad you found where the compilation is actually happening! There are several MSBuild workflow activities scattered about in the default build process template.
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, em, i, strike, strong, sub, sup, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Live Comment Preview