Bringing you news, technical articles, and other useful content about Visual Studio ALM and Team Foundation Server
More videos »
In a previous post, I have told you how the Configure Features wizard works to upgrade the team projects on your TFS server. It works great. However if you are an administrator of dozens of team projects, you don't want to walk through the wizard for each team project. Luckily we have a solution for you, which is outlined in this post.
You can either download the source code or execute the following steps.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.Integration.Server;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.Server.WebAccess.WorkItemTracking.Common;
namespace FeatureEnablement
{
class Program
static void Main(string[] args)
string urlToCollection = @"http://remi8:8080/tfs/defaultcollection";
Guid instanceId;
// Get the TF Request Context
using (DeploymentServiceHost deploymentServiceHost = GetDeploymentServiceHost(urlToCollection, out instanceId))
using (TeamFoundationRequestContext context = GetContext(deploymentServiceHost, instanceId))
// For each team project in the collection
CommonStructureService css = context.GetService<CommonStructureService>();
foreach (var project in css.GetWellFormedProjects(context))
// Run the 'Configuration Features Wizard'
ProvisionProjectFeatures(context, project);
}
private static DeploymentServiceHost GetDeploymentServiceHost(string urlToCollection, out Guid instanceId)
using (var teamProjectCollection = new TfsTeamProjectCollection(new Uri(urlToCollection), CredentialCache.DefaultCredentials))
const string connectionStringPath = "/Configuration/Database/Framework/ConnectionString";
var registry = teamProjectCollection.ConfigurationServer.GetService<Microsoft.TeamFoundation.Framework.Client.ITeamFoundationRegistry>();
string connectionString = registry.GetValue(connectionStringPath);
instanceId = teamProjectCollection.InstanceId;
// Get the system context
TeamFoundationServiceHostProperties deploymentHostProperties = new TeamFoundationServiceHostProperties();
deploymentHostProperties.ConnectionString = connectionString;
deploymentHostProperties.HostType = TeamFoundationHostType.Application | TeamFoundationHostType.Deployment;
return new DeploymentServiceHost(deploymentHostProperties, false);
private static TeamFoundationRequestContext GetContext(DeploymentServiceHost deploymentServiceHost, Guid instanceId)
using (TeamFoundationRequestContext deploymentRequestContext = deploymentServiceHost.CreateSystemContext())
// Get the identity for the tf request context
TeamFoundationIdentityService ims = deploymentRequestContext.GetService<TeamFoundationIdentityService>();
TeamFoundationIdentity identity = ims.ReadRequestIdentity(deploymentRequestContext);
// Get the tf request context
TeamFoundationHostManagementService hostManagementService = deploymentRequestContext.GetService<TeamFoundationHostManagementService>();
return hostManagementService.BeginUserRequest(deploymentRequestContext, instanceId, identity.Descriptor);
private static void ProvisionProjectFeatures(TeamFoundationRequestContext context, CommonStructureProjectInfo project)
// Get the Feature provisioning service ("Configure Features")
ProjectFeatureProvisioningService projectFeatureProvisioningService = context.GetService<ProjectFeatureProvisioningService>();
if (!projectFeatureProvisioningService.GetFeatures(context, project.Uri.ToString()).Where(f => (f.State == ProjectFeatureState.NotConfigured && !f.IsHidden)).Any())
// When the team project is already fully or partially configured, report it
Console.WriteLine("{0}: Project is up to date.", project.Name);
else
// find the valid process templates
IEnumerable<IProjectFeatureProvisioningDetails> projectFeatureProvisioningDetails = projectFeatureProvisioningService.ValidateProcessTemplates(context, project.Uri);
int validProcessTemplateCount = projectFeatureProvisioningDetails.Where(d => d.IsValid).Count();
if (validProcessTemplateCount == 0)
// when there are no valid process templates found
Console.WriteLine("{0}: No valid process template found!");
else if (validProcessTemplateCount == 1)
// at this point, only one process template without configuration errors is found
// configure the features for this team project
IProjectFeatureProvisioningDetails projectFeatureProvisioningDetail = projectFeatureProvisioningDetails.ElementAt(0);
projectFeatureProvisioningService.ProvisionFeatures(context, project.Uri.ToString(), projectFeatureProvisioningDetail.ProcessTemplateId);
Console.WriteLine("{0}: Configured using settings from {1}.", project.Name, projectFeatureProvisioningDetail.ProcessTemplateName);
else if (validProcessTemplateCount > 1)
// when multiple process templates found that closely match, report it
Console.WriteLine("{0}: Multiple valid process templates found!", project.Name);
NOTE: You need to run the code on a machine that has the Application Tier installed. So if you have Visual Studio on the Team Foundation Server you can run the code, else compile the code, copy the application to your Application Tier and run it from there.
The first four private methods (GetTfsTeamProjectCollection, GetDeploymentServiceHost, GetDeploymentRequestContext and GetTeamFoundationRequestContext) are only to create a requestContext, which you need to run the Configure Features. When we drill deeper into the ConfigureFeatures method. The method is using the class ProjectFeatureProvisioningService which is the service to Configure Features. Feature subscribe to that service, and the features that are subscribed is currently hard coded. Each feature implements an interface, including it is a hidden feature (that means that it is not critical to be configured), their State (NotConfigured, PartiallyConfigured or FullyConfigured), the Validation and the Provisioning.
The first step is to determine whether the team project needs to be configured. So we ask if all features are either configured (partially or fully) or hidden.
if (projectFeatureProvisioningService.GetFeatures(context, project.Uri.ToString()).All(f => (f.State != ProjectFeatureState.NotConfigured || f.IsHidden)))
Then it is validating all the process templates. During the validation the service is determining whether the settings stored in the process template can be applied to the current team project. Take a look at the deep dive post if you want to know more about the Validation process.
That method returns the validation details, which contains information like whether the process template is valid and the errors and warnings that it encountered during the validation. It counts the number of valid process templates, and acts upon it.
The current implementation logs a text message to the console window when there is no or multiple valid process templates, but you can use the details to find out why process templates are invalid in case a team project.
if (validProcessTemplateCount == 0) { // when there are no valid process templates found Console.WriteLine("{0}: No valid process template found!", project.Name); } ... else if (validProcessTemplateCount > 1) { // when multiple process templates found that closely match, report it Console.WriteLine("{0}: Multiple valid process templates found!", project.Name); }
If there is only one valid process template, the current implementation provisions that team project to configure the new features for TFS 2012.
else if (validProcessTemplateCount == 1) { // at this point, only one process template without configuration errors is found // configure the features for this team project IProjectFeatureProvisioningDetails projectFeatureProvisioningDetail = projectFeatureProvisioningDetails.ElementAt(0); projectFeatureProvisioningService.ProvisionFeatures(context, project.Uri.ToString(), projectFeatureProvisioningDetail.ProcessTemplateId); Console.WriteLine("{0}: Configured using settings from {1}.", project.Name, projectFeatureProvisioningDetail.ProcessTemplateName); }
Thanks for the code Ewald, but is there an updated version for the RTM / Update 1 bits? It doesn't appear that Microsoft.TeamFoundation.Server.WebAccess.WorkItemTracking.Common.dll is a valid assembly as of the RTM. And the MSDN Library has no API docs for the ProjectFeatureProvisioningService...
social.msdn.microsoft.com/.../en-US
(Did somebody forget to generate the TFS 2012 API docs...?)
Thanks,
Matt
My bad... I figured it out.
I was searching the assemblies at [TFS Install Path]\Tools, but the DLL I was missing was under [TFS Install Path]\ApplicationTier\Web Services\bin.
This doesn't resolve the issue that the MSDN documentation is not up-to-date, but at least the binaries are there.
We had our own issues after Update 1 when returning from GetDeploymentHostServer:
Unhandled Exception: Microsoft.TeamFoundation.Framework.Server.DatabaseInstanceException: TF30045: The instance information has not been configured or is not available for this Team Foundation Server. Please contact your Team Foundation Server administrator.
This was a result of not having updated the DLLs in the project after the update, and keeping those DLLs in the bin folder on the app tier. Once I updated the project's dlls to the new version, the program began working again.
I'm surprised at how fragile TFS seems to be and how inscrutable its errors are.
I don't know if the Update level matters, but with Update 2, I also needed to copy Microsoft.TeamFoundation.Server.Core.dll to get this to build.
@Burton, you are correct. I updated the post to correct that.