Recently in SCM Category

Our group at work is pushing to upgrade from Visual Studio 2010 to Visual Studio 2012 as quickly as possible. Because of that, I have been using VS2012 for a few weeks now trying to get a handle on the new release. After receiving a few different merge requests from the developers, I realized that as part of the upgrade, VS2012 did not bring in my settings for using Beyond Compare 3 Pro as my comparison tool.

Thankfully, setting it up appears to be virtually identical as to how it was in VS2010. I used this helpful StackOverflow question as a starting off point do the same in Visual Studio 2012.

For the sake of helping out other wayward Googlers, I have grabbed a few screenshots and shared that information on this blog. In addition, I went through a few different comparison and merge scenarios to make sure what was presented to me by BC3 was consistent between VS2010 and VS2012. Everything looks to be in order.

Compare:

  • Extension: .*
  • Operation: Compare
  • Command: Local Path and Filename for Beyond Compare Executable (bcomp.exe)
  • Arguments: %1 %2 /title1=%6 /title2=%7

Two way Merge:

  • Extension: .*
  • Operation: Merge
  • Command: Local Path and Filename for Beyond Compare Executable (bcomp.exe)
  • Arguments: %1 %2 /savetarget=%4 /title1=%6 /title2=%7

Three Way Merge (BC3 Pro Only):

  • Extension: .*
  • Operation: Merge
  • Command: Local Path and Filename for Beyond Compare Executable (bcomp.exe)
  • Arguments: %1 %2 %3 %4 /title1=%6 /title2=%7 /title3=%8 /title4=%9





As I discussed in part one of this article, TFS does not give a friendly view for an administrator to see what all builds are currently running. Because our TFS has so many team projects, it becomes extremely annoying to dig through each project to find out when a build has hung up a build agent.

Recently, we decided to retire our existing build machines and replace them with newer, more powerful machines, machines that would be capable of running more than one build at a time. Previously, we had several build machines (5-6), each of which had a single build controller and build agent running on them. Unfortunately, if six different developers on different development teams working in different Team Projects queued up a build on the same build machine, then five of the builds would be queued on that single machine, and none of those developers (or more importantly, me!) had an easy view into what they were waiting for. Ultimately, that one machine became wasteful bottleneck, as there would be several other unused machines.

Because of this, and other reasons, we revised our build machines’ topology. Now we are using a single build controller, and then we configured twelve new build agents spread across several machines. This allowed the build controller to make use of all the available build agents and solved the bottleneck caused by our prior topology.

Unfortunately, this made the console application that I wrote obsolete. Before, the console app only reported the build controllers that had builds running on them. This was fine, as long as there was a 1:1 relationship between build machines and build controllers. But now we only have one build controller, so if there was an issue with one of the twelve build agents assigned to that controller, the console app would not be useful to us as a troubleshooting tool.

To solve this problem, I further mangled/modified the console app posted on Bart Wullem’s blog to display the build controller’s name and the build agent’s name for all queued builds.

It is pretty simple to create a new console application in Visual Studio using the source I documented below. However, if you are anything like me you sure appreciate when someone shares a binary that they can use. You can download a copy here.

Installation Directions

  1. Unzip the contents to a folder. (Optional: For ease of use, add the folder you unzipped it to into your Windows PATH environment variable)
  2. Open the folder and edit the config file (TFSBuildQueue.exe.config), there is a single configuration entry in there for the URL of your TFS Project Collection (usually: http://yourtfsserver:8080/tfs/defaultcollection)
  3. Open a command console and set the width to at least 180 columns
  4. Change Directory to the path you unzipped TFSBuildQueue.zip to.
  5. Execute tfsbuildqueue.exe

Source Code

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Build.Client;

namespace TFSBuildQueue
{
    class Program
    {
        static void Main(string[] args)
        {
            int BuildCount = 0;
            string TFS_URL = ConfigurationManager.AppSettings["TFS_URL"];
            Console.WriteLine("\nTFS Build Queue");
            Console.WriteLine("===============\n");
            Console.WriteLine("Connecting to: " + TFS_URL + " and querying build controllers...");
            TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new            Uri(TFS_URL));
            IBuildServer bs = tfs.GetService<IBuildServer>();
            IQueuedBuildSpec qbSpec = bs.CreateBuildQueueSpec("*", "*");
            IQueuedBuildQueryResult qbResults = bs.QueryQueuedBuilds(qbSpec);


            // Define DataTable for storage and manipulation of currently queued builds.
            DataTable QBTable = new DataTable();
            QBTable.Columns.Add("BuildMachine");
            QBTable.Columns.Add("Project");
            QBTable.Columns.Add("BuildDefinition");
            QBTable.Columns.Add("BuildStatus");
            QBTable.Columns.Add("Priority");
            QBTable.Columns.Add("Date");
            QBTable.Columns.Add("ElapsedTime");
            QBTable.Columns.Add("User");


            // Query TFS For Queued builds and write each build to QBTable
            foreach (IQueuedBuild qb in qbResults.QueuedBuilds)
            {
                qb.Build.RefreshAllDetails();
                string RequestedBy = qb.RequestedBy.PadRight(18);
                if (qb.RequestedBy != qb.RequestedFor)
                {
                    RequestedBy = String.Concat(qb.RequestedBy, " (for ", qb.RequestedFor, ")").PadRight(18);
                }
                DateTime CurrentTime = DateTime.Now;
                TimeSpan ElapsedTime = CurrentTime.Subtract(qb.QueueTime);
                string ElapsedTimeString = ElapsedTime.ToString();
                String TFSET = ElapsedTimeString;
                String TFS_TEAMPROJECT;
                String BuildAgentStr;
                String BuildMachineStr;
                if (qb.Status.ToString() != "InProgress")
                {
                    TFS_TEAMPROJECT = "-------";
                    BuildAgentStr = "N/A";
                }
                else
                {
                    TFS_TEAMPROJECT = qb.Build.TeamProject;
                    BuildAgentStr = GetBuildAgent(qb.Build);
                }
                BuildMachineStr = qb.BuildController.Name + " (" + BuildAgentStr.ToUpper() + ")";

                QBTable.Rows.Add(
                BuildMachineStr.PadRight(46),
                TFS_TEAMPROJECT.PadRight(17),
                qb.BuildDefinition.Name.PadRight(28),
                qb.Status.ToString().PadRight(11),
                qb.Priority.ToString().PadRight(9),
                qb.QueueTime.ToString().PadRight(23),
                TFSET.PadRight(17),
                RequestedBy.PadRight(19)
                );
             BuildCount++;
            }

            // Sorts QBTable on Build controller then by date
            DataRow[] QBSorted = QBTable.Select("", "BuildMachine ASC, Date ASC");

            // Writes the headers 
            WriteHeaders();

            foreach (DataRow dataRow in QBSorted)
            {
                WriteReportLine(
                    dataRow[0].ToString(),
                    dataRow[1].ToString(),
                    dataRow[2].ToString(),
                    dataRow[3].ToString(),
                    dataRow[4].ToString(),
                    dataRow[5].ToString(),
                    dataRow[6].ToString(),
                    dataRow[7].ToString());
            }


            Console.WriteLine("\n\nTotal Builds Queued: " + BuildCount + "\n\n");
        }

        static void WriteHeaders()
        {
            Console.WriteLine("\n\n");
            Console.WriteLine("Build Controller (Agent)".PadRight(46) + " " +
                              "Project".PadRight(17) + " " +
                              "Build Definition".PadRight(28) + " " +
                              "Status".PadRight(11) + " " +
                              "Priority".PadRight(9) + " " +
                              "Date & Time Started".PadRight(23) + " " +
                              "Elapsed Time".PadRight(17) + " " +
                              "User".PadRight(19));
            Console.WriteLine("============================================".PadRight(46) + " " +
                              "=======".PadRight(17) + " " +
                              "================".PadRight(28) + " " +
                              "=======".PadRight(11) + " " +
                              "========".PadRight(9) + " " +
                              "=====================".PadRight(23) + " " +
                              "================".PadRight(17) + " " +
                              "============".PadRight(19));
        }

        static void WriteReportLine(string TFSBuildController, string TFSProject, string TFSBuildDefinition, string TFSBuildStatus, string TFSBuildPriority, string TFSBuildDateTime, string ElapsedTime, string TFSBuildUser)
        {
            Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7}", TFSBuildController, TFSProject, TFSBuildDefinition, TFSBuildStatus, TFSBuildPriority, TFSBuildDateTime, ElapsedTime, TFSBuildUser);
        }

        public static string GetBuildAgent(IBuildDetail build)  //IQueuedBuild.Build
        {
            foreach (var child in build.Information.Nodes)
            {
                string AgentName = ShowChild(child, 1);
                if (!string.IsNullOrEmpty(AgentName))
                {
                    return AgentName;
                }
            }
            return string.Empty;
        }

        static string ShowChild(IBuildInformationNode node, int level)
        {
            string levelStr = new string(' ', level * 4);
            foreach (var field in node.Fields)
            {
                if (field.Key == "ReservedAgentName")
                {
                    return field.Value;
                }
            }

            foreach (var child in node.Children.Nodes)
            {
                string AgentName = ShowChild(child, level + 1);
                if (!string.IsNullOrEmpty(AgentName))
                {
                    return AgentName;
                }
            }
            return string.Empty;

        }

    }

}

As a TFS administrator one of the questions I get asked frequently is why someone's build seems to be stuck in queue. Our TFS deployment has quite a few machines that each have a single build agent and build controller installed on them. To complicate things further, we have a few dozen different project collections in TFS.

Since you can only view builds queued up from a single team project, finding the source of these issues has always been a bit annoying.  You would have have to iterate through each of the team projects,  then view the queued builds in each team project until you found the culprit.  Depending on the number of team projects and builds,  this could be a very time consuming task.

Generally speaking, I would just write back and let them know I was keeping an eye on things and that they should just be patient for the build queue to clear itself up. But every now and then, something would hang indefinitely and the possibility of that always hung out in the back of my mind..

Thankfully, I found an article on Bart Wullem's blog, The Art of Simplicity, that set me off on the right direction: a console application that got all of the queued build information from the TFS API and wrote it out to the screen. Even better, the source code to the console app was published too.

I made a few changes; instead of hard coding the TFS URL into the app I added a config file and put the URL in there so that you could easily change it, I prettied up the output a little bit by sorting the builds by the Build Controller it's assigned to and then by the time it was queued, and lastly I added an elapsed time to the output.

In following Bart's example, I thought I'd share this and save another TFS administrator the effort I went through:


Update (12/28/2011): Fixed an issue where queued builds caused the application to crash.


using System;

using System.Configuration;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Data;

using Microsoft.TeamFoundation.Client;

using Microsoft.TeamFoundation.Build.Client;


namespace TFSBuildQueue

{

    class Program

    {

        static void Main(string[] args)

        {

            int BuildCount = 0;

            string TFS_URL = ConfigurationManager.AppSettings["TFS_URL"];

            Console.WriteLine("\nTFS Build Queue");

            Console.WriteLine("===============\n");

            Console.WriteLine("Connecting to: " + TFS_URL+ " and querying build controllers...");

            TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(TFS_URL));

            IBuildServer bs = tfs.GetService<IBuildServer>();

            IQueuedBuildSpec qbSpec = bs.CreateBuildQueueSpec("*", "*");

            IQueuedBuildQueryResult qbResults = bs.QueryQueuedBuilds(qbSpec);

            

            

            // Define DataTable for storage and manipulation of currently queued builds.

            DataTable QBTable = new DataTable();

            QBTable.Columns.Add("Controller");

            QBTable.Columns.Add("Project");

            QBTable.Columns.Add("BuildDefinition");

            QBTable.Columns.Add("BuildStatus");

            QBTable.Columns.Add("Priority");

            QBTable.Columns.Add("Date");

            QBTable.Columns.Add("ElapsedTime");

            QBTable.Columns.Add("User");


            // Query TFS For Queued builds and write each build to QBTable

            foreach (IQueuedBuild qb in qbResults.QueuedBuilds)

            {

                string RequestedBy = qb.RequestedBy.PadRight(18);

                if (qb.RequestedBy != qb.RequestedFor)

                {

                    RequestedBy = String.Concat(qb.RequestedBy," (for ",qb.RequestedFor,")").PadRight(18);

                }

                DateTime CurrentTime = DateTime.Now;

                TimeSpan ElapsedTime = CurrentTime.Subtract(qb.QueueTime);

                string ElapsedTimeString = ElapsedTime.ToString();

                String TFSET = ElapsedTimeString;

                String TFS_TEAMPROJECT;

                if (qb.Status.ToString() == "Queued")

                {

                    TFS_TEAMPROJECT = "-------";

                }

                else TFS_TEAMPROJECT = qb.Build.TeamProject;


                QBTable.Rows.Add(

                    qb.BuildController.Name.Replace(" - Controller", "").PadRight(17).ToUpper() + "(" + qb.BuildServer.Name.PadRight(17).ToUpper() +")",

                    TFS_TEAMPROJECT.PadRight(22),

                    qb.BuildDefinition.Name.PadRight(55),

                    qb.Status.ToString().PadRight(15),

                    qb.Priority.ToString().PadRight(12),

                    qb.QueueTime.ToString().PadRight(23),

                    TFSET.PadRight(18),

                    RequestedBy.PadRight(18)

                    );

                BuildCount++;

            }


            // Sorts QBTable on Build controller then by date

            DataRow[] QBSorted = QBTable.Select("", "Controller ASC, Date ASC");

            

            // Writes the headers 

            WriteHeaders();


            foreach (DataRow dataRow in QBSorted) 

            {

                WriteReportLine(

                    dataRow[0].ToString(),

                    dataRow[1].ToString(),

                    dataRow[2].ToString(),

                    dataRow[3].ToString(),

                    dataRow[4].ToString(),

                    dataRow[5].ToString(),

                    dataRow[6].ToString(),

                    dataRow[7].ToString());

            }


                            

            Console.WriteLine("\n\nTotal Builds Queued: " + BuildCount + "\n\n");

        }


        static void WriteHeaders()

        {

            Console.WriteLine("\n\n");

            Console.WriteLine("Controller (Agent)".PadRight(34) + " " +

                              "Project".PadRight(22) + " " +

                              "Build Definition".PadRight(55) + " " +

                              "Build Status".PadRight(12) + " " +

                              "Build Priority".PadRight(15) + " " +

                              "Date & Time Started".PadRight(23) + " " +

                              "Elapsed Time".PadRight(18) + " " +

                              "User".PadRight(18));

            Console.WriteLine("=================".PadRight(34) + " " +

                              "=======".PadRight(22) + " " +

                              "================".PadRight(55) + " " +

                              "============".PadRight(12) + " " +

                              "==============".PadRight(15) + " " +

                              "====================".PadRight(23) + " " +

                              "==================".PadRight(18) + " " +

                              "============".PadRight(18));

        }


        static void WriteReportLine(string TFSBuildController, string TFSProject, string TFSBuildDefinition, string TFSBuildStatus, string TFSBuildPriority, string TFSBuildDateTime, string ElapsedTime, string TFSBuildUser)

        {

            Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7}", TFSBuildController, TFSProject, TFSBuildDefinition, TFSBuildStatus, TFSBuildPriority, TFSBuildDateTime, ElapsedTime, TFSBuildUser);

        }


    }

}


Follow Brian

Archives