TFS 2010 – Managing Build Process Templates (what are those?)

If you haven’t noticed already in 2010. New build definitions no longer use the tfsBuild.proj file or the Microsoft.TeamFoundation.Build.targets file. With the switch to Windows Workflow as the base technology that the build process runs on we no longer need those MSBuild artifacts.

But we do need a way for you to define the build process. For that, we have XAML files checked into Version Control (example $/teamproject/BuildProcessTemplates/DefaultTemplate.xaml) that tell the build machine how to build whatever it is you want to build. These XAML files don’t have to live in this folder, like the 2008 TfsBuild.proj file, you can have them live anywhere. But the definition has to know where they live, so you can choose one to run your build. To make that user experience really simple, the build server has a list of the registered build process templates.

New Team Projects start off with 2 build process templates (3 if you count the lab template): the Default template and the Upgrade template. You may have noticed that when you create a new definition, it automatically chooses the DefaultTemplate for you. Even if you use the add button and create a template called A_template (alphabetically before defaulttemplate), a new build definition will still choose the default template.

But there’s another way to create a definition, you can checkin a TfsBuild.proj file into a new folder under the TeamBuildTypes folder like you would do in 2008. This will create a new build definition for you. However, this definition will use the upgrade template!

So, how does the server know which template to use? Well, when the XAML files are registered with the server, you can actually specify a type of Default, Upgrade, or Custom. Any templates you create will be defined as custom, but during team project creation, we create the default and upgrade templates for you and mark them with the appropriate type. Simple, right. But what if you want to change the default to point to one of your custom templates.

Unfortunately, there is no way to accomplish that without writing some code. Here is a sample console application that I created for this purpose. It allows you to see the list of registered templates, add your own, and even change which ones are the default and upgrade templates (there can be only one default and one upgrade template per team project).

 using System;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;

namespace ManageBuildTemplates
{
    class Program
    {
        /// <summary>
        /// args[0] - collection url
        /// args[1] - team project name
        /// args[2] - command (list, setDefault, setUpgrade, add, addDefault, addUpgrade, remove)
        /// args[3] - template server path (used for set,add,remove commands)
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            try
            {

                if (args.Length < 3)
                {
                    PrintUsage();
                    return;
                }

                TfsTeamProjectCollection collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(args[0]));
                IBuildServer buildServer = collection.GetService<IBuildServer>();

                switch (args[2].ToLower())
                {
                    case "add":
                        AddTemplate(buildServer, args[1], args[3], ProcessTemplateType.Custom);
                        ListTemplates(buildServer, args[1]);
                        break;

                    case "adddefault":
                        AddTemplate(buildServer, args[1], args[3], ProcessTemplateType.Default);
                        ListTemplates(buildServer, args[1]);
                        break;

                    case "addupgrade":
                        AddTemplate(buildServer, args[1], args[3], ProcessTemplateType.Upgrade);
                        ListTemplates(buildServer, args[1]);
                        break;

                    case "list":
                        ListTemplates(buildServer, args[1]);
                        break;

                    case "remove":
                        RemoveTemplate(buildServer, args[1], args[3]);
                        ListTemplates(buildServer, args[1]);
                        break;

                    case "setdefault":
                        SetTemplateType(buildServer, args[1], args[3], ProcessTemplateType.Default);
                        ListTemplates(buildServer, args[1]);
                        break;

                    case "setupgrade":
                        SetTemplateType(buildServer, args[1], args[3], ProcessTemplateType.Upgrade);
                        ListTemplates(buildServer, args[1]);
                        break;

                    default:
                        PrintUsage();
                        return;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine();
                Console.WriteLine("An error occured:");
                Console.WriteLine(ex.Message);
                Console.WriteLine();
            }
        }

        static void ListTemplates(IBuildServer buildServer, String teamProjectName)
        {
            Console.WriteLine();
            Console.WriteLine("Build Process Templates for Team Project: {0}", teamProjectName);
            Console.WriteLine();
            Console.WriteLine("Type            Filename");
            Console.WriteLine("--------------- --------------------------------------------");

            IProcessTemplate[] templates = buildServer.QueryProcessTemplates(teamProjectName);
            foreach (IProcessTemplate pt in templates)
            {
                Console.WriteLine("{0,15} {1}", pt.TemplateType.ToString(), pt.ServerPath);
            }

            Console.WriteLine();
        }

        static void SetTemplateType(IBuildServer buildServer, String teamProjectName, String serverPath, ProcessTemplateType newType)
        {
            Console.WriteLine();
            Console.WriteLine("Changing the type of Build Process Template");
            Console.WriteLine("    Team project: {0}", teamProjectName);
            Console.WriteLine("    Server path:  {0}", serverPath);
            Console.WriteLine();

            IProcessTemplate[] templates = buildServer.QueryProcessTemplates(teamProjectName);

            if (newType == ProcessTemplateType.Default || newType == ProcessTemplateType.Upgrade)
            {
                // Make sure there isn't already a template with that type
                // - there can be only one upgrade or default template for a team project
                foreach (IProcessTemplate pt in templates)
                {
                    if (pt.TemplateType == newType)
                    {
                        Console.WriteLine("Existing template found with the type '{0}'.", newType);
                        Console.WriteLine("    Server path: {0}", pt.ServerPath);
                        Console.WriteLine("    Changing type for this template to custom.");
                        Console.WriteLine();
                        pt.TemplateType = ProcessTemplateType.Custom;
                        pt.Save();
                    }
                }

            }

            foreach (IProcessTemplate pt in templates)
            {
                if (pt.ServerPath.Equals(serverPath, StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine("Template found.");
                    Console.WriteLine("    Changing type for the template to '{0}'.", newType);
                    Console.WriteLine();
                    pt.TemplateType = newType;
                    pt.Save();
                    return;
                }
            }

            Console.WriteLine("Template NOT found.");
            Console.WriteLine();
        }

        static void AddTemplate(IBuildServer buildServer, String teamProjectName, String serverPath, ProcessTemplateType newType)
        {
            Console.WriteLine();
            Console.WriteLine("Adding the {0} Build Process Template", newType.ToString());
            Console.WriteLine("    Team project: {0}", teamProjectName);
            Console.WriteLine("    Server path:  {0}", serverPath);
            Console.WriteLine();

            IProcessTemplate template = buildServer.CreateProcessTemplate(teamProjectName, serverPath);
            template.Save();
            Console.WriteLine("Template added (as a custom build template).");
            Console.WriteLine();

            if (newType == ProcessTemplateType.Default || newType == ProcessTemplateType.Upgrade)
            {
                SetTemplateType(buildServer, teamProjectName, serverPath, newType);
            }
        }

        static void RemoveTemplate(IBuildServer buildServer, String teamProjectName, String serverPath)
        {
            Console.WriteLine();
            Console.WriteLine("Removing the Build Process Template");
            Console.WriteLine("    Team project: {0}", teamProjectName);
            Console.WriteLine("    Server path:  {0}", serverPath);
            Console.WriteLine();

            IProcessTemplate[] templates = buildServer.QueryProcessTemplates(teamProjectName);
            foreach (IProcessTemplate pt in templates)
            {
                if (pt.ServerPath.Equals(serverPath, StringComparison.OrdinalIgnoreCase))
                {
                    ProcessTemplateType type = pt.TemplateType;
                    pt.Delete();
                    Console.WriteLine("Template found and removed.");
                    if (type != ProcessTemplateType.Custom)
                    {
                        Console.WriteLine("Note: You have removed the '{0}' template for this team project.", type);
                    }
                    Console.WriteLine();

                    return;
                }
            }

            Console.WriteLine("Template not found.");
            Console.WriteLine();
        }

        static void PrintUsage()
        {
            Console.WriteLine("Usage:");
            Console.WriteLine("ManageBuildTemplates <tfsCollectionUrl> <teamProjectName> add|addDefault|addUpgrade|list|remove|setDefault|setUpgrade [templateServerPath]");
        }
    }
}

The writelines and comments should give you enough information on how to use the app, but just in case here are some examples:

1) Add my template for a XAML file I already checked in:

>ManageBuildTemplates.exe https://jpricket-test:8080/tfs/TestCollection0 TestProject add  $/TestProject/BuildProcessTemplates/MyTemplate.xaml

2) Change the default template to be my template:

>ManageBuildTemplates.exe https://jpricket-test:8080/tfs/TestCollection0 TestProject setDefault $/TestProject/BuildProcessTemplates/MyTemplate.xaml

I hope you enjoy it!