How to Configure Features for dozens of team projects

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.

Create and run the application

  1. Open Visual Studio 2012
  2. Create a new “C# Console Application” Project
  3. Open the folder C:\Program Files\Microsoft Team Foundation Server 11.0\Application Tier\Web Services\bin on the Application Tier.
  4. Copy the following files over to your local machine and add a reference to these local copies of the assembly
    • Microsoft.TeamFoundation.Framework.Server.dll
    • Microsoft.TeamFoundation.Server.Core.dll (Required if 2012 Update 2 was installed)
    • Microsoft.TeamFoundation.Server.WebAccess.WorkItemTracking.Common.dll
  5. Add references to the following assemblies. These are located in the folder C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ReferenceAssemblies\v2.0
    • Microsoft.TeamFoundation.Client.dll
    • Microsoft.TeamFoundation.Common.dll
    • Microsoft.TeamFoundation.WorkItemTracking.Client.dll
  6. Open program.cs and overwrite the contents of the file with the following code and change the highlighted url.
  7. Copy your application to C:\Program Files\Microsoft Team Foundation Server 11.0\Application Tier\Web Services\bin on the Application Tier and run it from there.
    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.

Understand the application

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.

IEnumerable<IProjectFeatureProvisioningDetails> projectFeatureProvisioningDetails = projectFeatureProvisioningService.ValidateProcessTemplates(context, project.Uri);

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.

int validProcessTemplateCount = projectFeatureProvisioningDetails.Where(d => d.IsValid).Count();

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);
}