Take the 2-minute tour ×

In our project, we'd like to have our TFS build put each project into its own folder under the drop folder, instead of dropping all of the files into one flat structure. To illustrate, we'd like to see something like this:


This is basically the same question as was asked here, but now that we're using workflow-based builds, those solutions don't seem to work. The solution using the CustomizableOutDir property looked like it would work best for us, but I can't get that property to be recognized. I customized our workflow to pass it in to MSBuild as a command line argument (/p:CustomizableOutDir=true), but it seems MSBuild just ignores it and puts the output into the OutDir given by the workflow.


Any idea why my CustomizableOutDir parameter isn't getting recognized, or if there's a better way to achieve this?

share|improve this question
Consider to use package PublishedApplications available on Nuget nuget.org/packages/PublishedApplications –  Michael Freidgeim Dec 11 '12 at 20:50

10 Answers 10

up vote 12 down vote accepted

I figured out a nice way to do it. It turns out that since you can set the OutDir to whatever you want within the workflow, if you set it to the empty string, MSBuild will instead use the project-specific OutputPath. That lets us be a lot more flexible. Here's my entire solution (based on the default build workflow):


String.Format("/p:CommonOutputPath=""{0}\\""", outputDirectory)


    <OutputPath Condition=" '$(CommonOutputPath)'=='' ">bin\Release\</OutputPath>
    <OutputPath Condition=" '$(CommonOutputPath)'!='' ">$(CommonOutputPath)YourProjectName\bin\Release\</OutputPath>


share|improve this answer
Does this not cause problems with MSTest? –  MrHinsh Jun 9 '10 at 14:09
We're not using MSTest for this project (or any automated testing, for that matter), so I honestly don't know. If you find out one way or another, though, that'd be great info to post. –  Jonathan Schuster Jun 9 '10 at 15:26
Works like a charm. +1 –  JanW Jul 13 '11 at 12:37

I solved this problem, too, and I think it's cleaner than the existing solutions on this thread.

  • Before the Run MSBuild for Project activity, I added an Assign activity: projectName = Regex.Replace(New FileInfo(localProject).Name, "\.sln$", "").
  • Next I added a Create Directory activity: outputDirectory + "\" + projectName
  • Finally in the MSBuild activity I changed OutDir to outputDirectory + "\" + projectName.

The template already populates localProject with the full path name of the the .sln file being built on the Agent, e.g., c:\build\path\to\MySolution.sln. The assign activity chops off the path and extension, placing the output in MySolution. You'll need to create the projectName variable, and import System.Text.RegularExpressions and System.IO.

The advantage over OP's solution is that you don't have to edit each .csproj, that information is inferred from the solution file's name.

share|improve this answer
This worked perfectly and did exactly what I want with very little effort. The only change that I made was to use Path.Combine instead of manually concatenating paths. For those curious, unit tests seem to work fine when using this route. Wish I could upvote you twice! –  Case Dec 6 '11 at 19:35
This works with MSTest? –  Juan Zamudio Feb 1 '12 at 2:08
All this does is put your output in a solution level folder... If you have more than one project in the solution they will still be grouped into that solution level folder. –  Vaccano Feb 7 '12 at 23:48

We had to do this to bypass the problem where we have a Silverlight and a .Net library with the same name for CSLA serialization. The library would be overwritten and our tests would fail.

I used Jonathan's answer and Jim Lamb's post, but I found that you also need to set OutDir to empty.

So, you need to do these parameters for the MSBuild activities (if you use the following Macro, you need to set the activity parameters for Clean too, otherwise you get warnings that OutputPath is not set):

  • Set CommandLineArguments to String.Format("/p:SkipInvalidConfigurations=true;TeamBuildOutDir=""{0}"" {1}", BinariesDirectory, MSBuildArguments)
  • Set OutDir to empty (was BinariesDirectory)

I have also created a macro that you can run in visual studio that removes the OutputPath from the configurations, and adds a PropertyGroup that contains the OutputPath for all configs like so :

<PropertyGroup Label="OutputPathLabel">
  <OutputPath Condition="'$(TeamBuildOutDir)'=='' ">bin\$(Configuration)\</OutputPath>
  <OutputPath Condition="'$(TeamBuildOutDir)'!='' ">$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)\</OutputPath>

Here's the Macro :

Public Sub SetTeamBuildOutDir()

    Dim projectObjects = DTE.Solution.Projects

    For Each project In projectObjects

        If project.ProjectItems IsNot Nothing Then
        End If

End Sub

Sub SetTeamBuildOutDirRecursive(ByVal proj As Project)
    If proj.ConfigurationManager Is Nothing Then
        For Each subProj As ProjectItem In proj.ProjectItems
            If subProj.SubProject IsNot Nothing Then
            End If
    End If

End Sub

Sub SetTeamBuildOutDir(ByVal project As Project)
    'Do not handle .vdproj
    If project.FullName.ToLower().EndsWith(".vdproj") Then
        Exit Sub
    End If

    Dim needToSave = False
    Dim msproject = ProjectRootElement.Open(project.FullName)
    Dim outputPathGroupExists = False
    Dim outputPropertyGroup As ProjectPropertyGroupElement = Nothing
    Dim lastConfigPropertyGroup As ProjectPropertyGroupElement = Nothing

    For Each propertyGroup In msproject.PropertyGroups

        If propertyGroup.Label = "OutputPathLabel" Then
            outputPathGroupExists = True
            outputPropertyGroup = propertyGroup
        End If

        If Not String.IsNullOrEmpty(propertyGroup.Condition) AndAlso _
            propertyGroup.Condition.TrimStart().StartsWith("'$(Configuration)") Then

            lastConfigPropertyGroup = propertyGroup
        End If

        'Remove the OutputPath from the configurations
        Dim outputPathElement As ProjectPropertyElement = Nothing
        For Each element As ProjectPropertyElement In propertyGroup.Children
            If element.Name = "OutputPath" Then
                outputPathElement = element
            End If
        If outputPathElement IsNot Nothing Then
            needToSave = True
        End If

    'If we want to always remove the group and add it back (in case of modifications to the group)
    'If outputPathGroupExists Then
    '    msproject.RemoveChild(outputPropertyGroup)
    '    outputPathGroupExists = False
    'End If

    If Not outputPathGroupExists Then
        Dim propertyGroup = msproject.CreatePropertyGroupElement()
        propertyGroup.Label = "OutputPathLabel"
        'Need to insert the PropertyGroup before the CSharp targets are included
        msproject.InsertAfterChild(propertyGroup, lastConfigPropertyGroup)

        Dim isDbProject = project.FullName.ToLower().EndsWith(".dbproj")

        Dim outputEmpty = propertyGroup.AddProperty("OutputPath", IIf(Not isDbProject, "bin\$(Configuration)\", "sql\$(Configuration)\"))
        outputEmpty.Condition = "'$(TeamBuildOutDir)'=='' "

        Dim outputTeamBuild = propertyGroup.AddProperty("OutputPath", "$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)\")
        outputTeamBuild.Condition = "'$(TeamBuildOutDir)'!='' "

        needToSave = True
    End If

    If needToSave Then
        'checkout the project file with tfs
        Shell("C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe checkout " & project.FullName, , True)

        'Save the project file
    End If
End Sub

Hope this helps!!!

share|improve this answer
Thanks for the macro –  stuartd Jul 14 '11 at 14:54
Note however that WebApplication projects need an OutputPath of just bin\ rather than bin\$(Configuration)\ –  stuartd Jul 19 '11 at 14:29

The steps here don't require a project file modification - http://lajak.wordpress.com/2011/05/07/customize-binaries-folder-in-tfs-team-build/

share|improve this answer

Here's a very simple solution that requires no modification to source files or project files. When setting up your build definition's Process -> Projects to Build, instead of specifying your .sln file in the projects to build, add each project (.csproj or .vbproj). Now inside the "Run MSBuild for Project" step of your workflow, change the OutDir property to the following:

outputDirectory + "/" + serverBuildProjectItem.Substring(serverBuildProjectItem.LastIndexOf("/"), serverBuildProjectItem.Length - serverBuildProjectItem.LastIndexOf("/")).Replace(".csproj", String.Empty)

This will place each project's build output in to a subdirectory named after the project.

share|improve this answer
Rajani suggested (see below) to use System.IO.Path.GetFileNameWithoutExtension(serverBuildProjectItem ) instead –  Michael Freidgeim Dec 8 '12 at 6:33

I’ve installed the  package PublishedApplications from Nuget for each Executable in My Solution and it created subfolders under _PublishedApplications folder for each project during the build.

share|improve this answer

I haven't played with getting TFS/MSBuild to put the output files in separate folders, so can't give a direct answer. However, here are a couple of suggestions that I didn't spot in your link:

  • You could add post-build steps to the projects that copy the required files to a "deployment" structure. (This of course would also run on dev machines, which might be a pain). We use this approach for our libraries, whch are built and then copied into a shared libs (binaries) folder for other projects to reference them from.

  • You could add an MSBuild target to copy the required files where you want them. We've overridden the default "copy to drop folder" targets to copy the files to another folder, obfuscate them, digitally sign them, build them into an installer, digitally sign the installer, and then copy it (and other useful stuff like the obfuscation map files) and a list of changes since the last build to the drop folder. Ultimately adding your own post-build target gives you maximum control over exactly what gets put where. (On the minus side, you may have to manually add any new dlls or exes to the post-build copy target, which could be an irritation)

share|improve this answer


OutputDirectory + "\" + System.IO.Path.GetFileNameWithoutExtension(serverBuildProjectItem)
share|improve this answer
It should be actually a comment to Andy Morris answer stackoverflow.com/a/11748510/52277 –  Michael Freidgeim Dec 8 '12 at 6:31

Not sure if you can still get team build 2010 to use the latest version of msbuild, but there's a new property /p:GenerateProjectSpecificOutputFolder=true when specified will drop the bits into $(OutDir)\$(ProjectName)\ for each project.

share|improve this answer

I'm nearly finished whipping up a 2010 build process that is capable of doing this without having to modify any source files. It's tailored probably for the environment where I work, but I think it's pretty rad. You might be surprised how much work went into getting this doing what I need it to. From the "Edit Build Definition" window, I can configure exactly which projects in my solution I want to output to the drop folder. Each project within the .sln will be output to \\\. Hopefully that makes sense. It even puts the output of a web project exactly where you would expect it to be instead of having to drop into the _PublishedWebsites directory. This also means the "logs" folder is in a separate directory from all of the output my solution (.sln) produces (at \\logs).

The only thing I haven't finished is the bit about getting the tests to work right and I expect to have that done soon. I can't provide any source code (yet - have to ask for permission first), but I can at least tell you it's possible for now. :)

share|improve this answer
Did you get this to a point that you can provide source code? –  Ezweb Aug 19 '11 at 21:40
We're developers, anything is possible. :) However, without source or additional information this answer is not very useful. –  Case Dec 6 '11 at 19:02

Your Answer


