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, 31 January 2011 11:44:39 (Pacific Standard Time, UTC-08: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, 29 April 2011 12:47:53 (Pacific Daylight Time, UTC-07: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, 29 April 2011 12:48:39 (Pacific Daylight Time, UTC-07: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, 29 April 2011 14:20:54 (Pacific Daylight Time, UTC-07: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, 11 May 2011 13:23:33 (Pacific Daylight Time, UTC-07:00)
I have the exact same problem - how do I clear the OutDir parameter?
Friday, 22 July 2011 12:36:02 (Pacific Daylight Time, UTC-07:00)
Is there a way to supress the web projects BIN directory being created in the route of the TeamBuildOutDir ??
JIm Groves
Thursday, 28 July 2011 14:48:02 (Pacific Daylight Time, UTC-07: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, 05 October 2011 07:34:50 (Pacific Daylight Time, UTC-07: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, 01 February 2012 16:16:09 (Pacific Standard Time, UTC-08: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, 01 February 2012 17:26:52 (Pacific Standard Time, UTC-08: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, 01 February 2012 17:51:35 (Pacific Standard Time, UTC-08: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, 01 February 2012 18:32:25 (Pacific Standard Time, UTC-08: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, 01 February 2012 18:34:58 (Pacific Standard Time, UTC-08: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.
Tuesday, 03 July 2012 01:20:27 (Pacific Daylight Time, UTC-07:00)
I got this error when using the example in a web app project
c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets (490): The OutDir property must end with a trailing slash.

I assume you have to put the backslash in.
Viv
Thursday, 15 November 2012 12:39:51 (Pacific Standard Time, UTC-08:00)
Sorry for the lame question but how do you
you must clear the OutDir parameter so it doesn't contain the default 'outputDirectory' value


Do you change the value for Initialize OutputDirectory in the properties window from:
If(platformConfiguration.IsEmpty Or BuildSettings.PlatformConfigurations.Count = 1, BinariesDirectory, If(platformConfiguration.IsPlatformEmptyOrAnyCpu, BinariesDirectory + "\" + platformConfiguration.Configuration, BinariesDirectory + "\" + platformConfiguration.Platform + "\" + platformConfiguration.Configuration))


to string.empty?

Daa
Saturday, 08 December 2012 19:13:06 (Pacific Standard Time, UTC-08:00)
I have the exact same problem - how do I clear the OutDir parameter?
To clear the OutDir parameter, go to the "Run MSBuild for Project" activity that we've customized:
1. Right-click for properties
2. In properties (listed alphabetically), find OutDir
3. In OutDir's value box you'll see outputDirectory (someone explained earlier where that was from)
4. Delete this value and enter a pair of double-quotes "".
5. Save your template and proceed!

Also, as Viv mentioned earlier, you will need to leave a trailing slash after your value within <OutDir>

This was real treat finding this, Ed! I've been hacking at this for a week on a solution that contained, precisely, web projects and non-web projects.
Sunday, 03 February 2013 20:33:49 (Pacific Standard Time, UTC-08:00)
Unfortunately a side effect of turning on the customizable directories is that automated tests are no longer executed due to the test DLLs not being automatically placed in the Binaries directory. Any suggestions as to how to get around this without developers having to remember to modify the OutputPath as per your post? I tried the following:
- changing the Find Test Assemblies pattern (see http://stackoverflow.com/questions/8132735/how-to-specify-correct-search-mask-for-test-assembly-file-specification-dialog ) to point to the Build Directory but that then finds both Obj and Bin versions of the Test DLLs, which in turn produces errors.
- the above, but changed the "Test Assembly Filespec" to "**\Release\*test.dll" but it says pattern is not a valid search pattern

Thanks
Brett
Brett
Friday, 03 May 2013 14:35:25 (Pacific Daylight Time, UTC-07:00)
Any suggestions for web site projects? I can't use the opt-in because web site projects don't have project files. In testing this solution, I can't get the web site projects to be copied to the drop folder.
Kevin Verble
Tuesday, 10 September 2013 10:20:59 (Pacific Daylight Time, UTC-07:00)
I was unable to get this working, until I changed "<OutputPath>" to "<OutDir>" as shown below in my n on-web app csproj files. Is this a typo above, or should it have worked? It's also possible another propertyGroup was overriding it? I don't know how it would know whether to use the <outputpath> from the platform/configuration propertygroup or from my teambuildoutdir propertygroup?

<!-- Customizable Output Directory Opt-In for TFS Build (non-web application projects) -->
<PropertyGroup Condition="$(TeamBuildOutDir) != '' ">
<OutDir>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)</OutDir>
</PropertyGroup>
Tony Elsmore
Wednesday, 29 January 2014 10:23:31 (Pacific Standard Time, UTC-08:00)
I would be interested in Brett's query, as well.

It does seem that the only alternative is to add an OutDir (or OutputPath?) to the test csproj in order to force it's binaries to drop to the root build output path like it normally would. No too big of a deal, I suppose, unless you're dealing with a very large project (i.e.: 11 mixed apps + their test projs).
Tuesday, 04 February 2014 13:46:45 (Pacific Standard Time, UTC-08:00)
Using .NET 4.5, this is much simpler, all you need is a build argument as per: http://blog.stangroome.com/2012/05/10/override-the-tfs-team-build-outdir-property-net-4-5/
Kevin
Wednesday, 23 July 2014 09:47:02 (Pacific Daylight Time, UTC-07:00)
So I have a huge solution with over 30 projects. I'm only concerned with getting the build output for one project (a windows service) into its own output directory. The rest I want to be dropped in the solution output directory like usual.

Tried implementing the above solution, and only putting the "Opt-In" property group in the one project file.

If I also remove the OutDir property value from the "Run MSBuild for Project" activity in the workflow as mentioned in one of the comments above, then the windows service project gets output to the correct directory (bin\<solution name>\<project name>), but all the other project outputs do not get put in the common solution output dir, instead they individually get put in src\<solution name>\<project name>\bin.

If I do not remove the OutDir property value, then everything else gets put in the right place, but I don't get my custom output directory for the windows service project.

How can I get the custom output for just my windows service project without affecting the output for all the other projects?
Jesse Kindwall
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">.  

[Captcha]Enter the code shown (prevents robots):

Live Comment Preview