TFS 2010 - Managing the hidden Build Service Host objects

If you use the TFS Build Automation Object Model, then you may have noticed that we have objects that are related to Controllers and Agents, but that don't show up in the UI. These objects are called Build Service Host objects or IBuildServiceHost objects. The name doesn't really suggest very much. So, let me give you some more info. These objects represent the physical location of the Controller and/or Agent objects. The name property is usually the machine name that the Controller or Agent lives on. In earlier versions, this was simply the Machine Name property on the Agent. In TFS 2010, we needed a better way to share this for Controllers and Agents. Thus the Build Service Host object was developed. Unfortunately, we tried to hide this implementation detail from the user. The UI suggests that something is there, but does not allow the user to do much with it. For instance, you can't see a list of them in the UI. Nor can you delete one from the UI (unless you unregister the Build Machine from the server).

Fortunately, the Object Model gives you complete control over these objects. Keep in mind that every Controller and Agent MUST be tied to a BuildServiceHost (BSH). So, you can't delete the BSH without removing the Controller and Agents as well. But since the UI allows you full control over the Controllers and Agents, I have skipped that part in this post.

So, what is this post really for? Well, there are ways in TFS 2010 to end up with a BSH stuck in your server with no way to remove it from the UI (because of the UI limitations mentioned earlier). So, I wanted to provide some sample code that gives you the easiest way to manage these hiddden objects. This sample app allows you to List, Delete, Rename and even Create a BSH on the server. However, create is only here so that I could easily test the application. I don't recommend creating a BSH directly. They are created automatically when you register a Build Machine. I thought Rename might be useful, but I can't think of any great reasons to use it. If you really want to rename your Build Machine this is not going to do that. It will only rename the object and change the BaseUrl used for communication.

Anyway... here's the code...

using System;

using Microsoft.TeamFoundation.Build.Client;

using Microsoft.TeamFoundation.Client;

namespace BuildServiceHostUtil

{

    class Program

    {

        static void Main(string[] args)

        {

            try

            {

                if (args.Length < 2)

  {

                    ShowUsage();

                    return;

                }

                String command = GetCommandParameter(args, 0, "command");

                Uri collectionUri = new Uri(GetCommandParameter(args, 1, "collection uri"));

                TfsTeamProjectCollection collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collectionUri);

                switch (command.ToLowerInvariant())

                {

                    case "list":

     DoList(collection);

                        break;

                    case "create":

                        DoCreate(collection,

                            GetCommandParameter(args, 2, "build service host name"));

                        DoList(collection);

                        break;

                    case "delete":

                        DoDelete(collection,

                            GetCommandParameter(args, 2, "build service host name"));

              DoList(collection);

                        break;

                    case "rename":

                        DoRename(collection,

                            GetCommandParameter(args, 2, "existing name"),

                            GetCommandParameter(args, 3, "new name"));

                        DoList(collection);

                        break;

                    default:

                        ShowUsage();

                        return;

                }

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.ToString());

                Console.WriteLine();

                ShowUsage();

            }

        }

        private static void DoList(TfsTeamProjectCollection collection)

        {

            IBuildServer buildServer = collection.GetService<IBuildServer>();

            foreach (IBuildServiceHost bsh in buildServer.QueryBuildServiceHosts("*"))

            {

                Console.WriteLine("{0} ({1})", bsh.Name, bsh.Uri);

          Console.WriteLine(" {0} - {1} agent(s)", bsh.Controller == null ? "no controller" : "1 controller", bsh.Agents.Count);

                Console.WriteLine(" {0}", bsh.BaseUrl.AbsoluteUri);

                Console.WriteLine();

            }

     }

        private static void DoDelete(TfsTeamProjectCollection collection, string hostName)

        {

            IBuildServer buildServer = collection.GetService<IBuildServer>();

            IBuildServiceHost bsh = buildServer.GetBuildServiceHost(hostName);

            bsh.Delete();

        }

        private static void DoCreate(TfsTeamProjectCollection collection, string hostName)

        {

            IBuildServer buildServer = collection.GetService<IBuildServer>();

            IBuildServiceHost bsh = buildServer.CreateBuildServiceHost(hostName, "http", hostName, 8181);

            bsh.Save();

        }

        private static void DoRename(TfsTeamProjectCollection collection, string existingHostName, string newHostName)

        {

            IBuildServer buildServer = collection.GetService<IBuildServer>();

            IBuildServiceHost bsh = buildServer.GetBuildServiceHost(existingHostName);

            bsh.Name = newHostName;

            UriBuilder uriBuilder = new UriBuilder(bsh.BaseUrl);

  uriBuilder.Host = newHostName;

            bsh.BaseUrl = uriBuilder.Uri;

            bsh.Save();

        }

        private static String GetCommandParameter(string[] args, int index, String parameterName)

        {

            if (args.Length <= index)

            {

                throw new Exception(String.Format("Parameter '{0}' is missing. See Usage.", parameterName));

            }

            return args[index];

        }

        private static void ShowUsage()

        {

            Console.WriteLine("Usage:");

            Console.WriteLine();

            Console.WriteLine("BuildServiceHostUtil.exe <command> <collectionUri> [<commandParameters>]");

            Console.WriteLine();

            Console.WriteLine(" Commands: List, Create, Delete, Rename");

            Console.WriteLine();

            Console.WriteLine(" Parameters: List - none ");

            Console.WriteLine(" Create - Service Host Name (usually the machine name)");

            Console.WriteLine(" Delete - Service Host Name (usually the machine name)");

            Console.WriteLine(" Rename - <Old Name> <New Name> (changes the base url also)");

            Console.WriteLine();

        }

    }

}

Build On!