Share via


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!

Comments

  • Anonymous
    May 11, 2011
    Jason, I built a VSX package that uses the value of the selected build in Build Explorer.  To do this I used the Microsoft.VisualStudio.TeamFoundation.Build assembly.  This was a Proof of Concept and this assembly is key to the deal.  The app works great and lots of kudos came .  Now the problem-  I understand that this is an unsupported API and since the app will be on several thousand developers desktops the big question  has been raised.   Can we depend on this"Private Assembly" to stay there and be stable?

  • Anonymous
    June 01, 2011
    Hi Danny (or is it Danny?) That assembly will be there for a while to come. If we do move it or remove it, the public extensibility classes that it provided will simply move. They are public and will be supported. I hope that answers your question. Thanks, Jason