How to Configure Features for dozens of team projects

How to Configure Features for dozens of team projects

Rate This
  • Comments 11

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

Leave a Comment
  • Please add 4 and 8 and type the answer here:
  • Post
  • 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.

  • Is there an updated version of this code to target TFS 2013?

  • @Angela - I replaced the above TFS assemblies with their v12.0 equivalents and that got me 90% of the way.  Right now, I'm getting a compilation error on the return type of the GetContext method (which returns type TeamFoundationRequestContext).  The error indicates:

    "The type 'Microsoft.VisualStudio.Services.WebApi.VssHttpClientBase' is defined in an assembly that is not referenced. You must add a reference to assembly 'Microsoft.VisualStudio.Services.WebApi, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'."

    I added this and was able to compile. But I don't see the association in the MSDN documentation and it adds a bunch of other assemblies to my project that I'm not using so I'm not sure why this namespace is needed.

    If Ewald is still monitoring this, maybe he can elaborate...?

  • FYI... I've created an MSDN Forums question documenting where I'm at with this if anybody wants to contribute suggestions...

    social.msdn.microsoft.com/.../feature-enablement-app-fails-with-tfs-2013

    Thanks!

  • FYI: we have an updated version of the tool which works with TFS 2013. You can find source code here: features4tfs.codeplex.com

    Thanks,

    Oleg

  • How to update projects upgraded from TFS 2008 to TFS 2013..  will the above source code works for that too?

  • The code works only from 2010 and beyond. You first need to do the steps to add the changes between 2008 and 2010. There is a blog post that describes how to achieve this: blogs.msdn.com/.../configure-features-for-process-templates-based-on-v-4-2-process-templates.aspx

Page 1 of 1 (11 items)