# Tuesday, January 29, 2013

TFS has some pretty powerful functionality out of the box one of them is the merge functionality. However I’ve always felt it missed what I thought was a rather nice feature to have, and that is the ability to see a complete list of PBI’s that would be affected by a merge from one branch to another. Now you could argue that you’ll get this anyway by just looking at your PBI’s. However if you find yourself cherry picking change sets  from one branch to the other e.g. you want a finished component to use in your own branch another team has just completed or you only want to release a certain PBI to live, its nice to know if that PBI turns up.

I must point out before continuing that this approach is far from infallible and relies on good housekeeping on the part of your developers i.e. they associate their checked in change sets with work items in TFS. You could also argue that TFS already gives you the PBI’s associated with a change set. It does but you have to go through a bit of pain to actually get to it, that is you open each change set and then find the work items associated with that change set and then the PBI associated with that work item.

The Code
I have put this together as a console app you will need to reference the following in your app in order for this to work. Be sure if you have both the TFS 2010 and TFS 2012 clients installed that you do not mix DLL versions!

  • Microsoft.TeamFoundation.Client
  • Microsoft.TeamFoundation.VersionControl.Client
  • Microsoft.TeamFoundation.WorkItemTracking.Client

Firstly we just do a bit of setup such as the TFS server URL and credentials, the from and to branches etc.

   1:  //The url of your tfs server
   2:  // Don't forget if you have more than one collect you will have to indicate that in the URL!
   3:  private const string TfsServer = "http://localhost:8080/tfs";
   5:  //Replace these with your tfs username and password
   6:  private const string TfsUserName = "tfsusername";
   7:  private const string TfsPassword = "tfspassword";
   9:  //This is the branch that you are merging from
  10:  private const string FromBranch = "$/TestSource/Dev";
  12:  //This is the branch you are merging to.
  13:  private const string ToBranch = "$/TestSource/Main";
  15:  //In my TFS the PBI is called a Product Backlog Item it may be called 
  16:  // something else depending on the template you use.
  17:  const string ProductBackLogItem = "Product Backlog Item";
  18:  const string SprintBacklogTask = "Sprint Backlog Task";
  20:  static readonly List<int> WorkItemCache = new System.Collections.Generic.List<int>();
  22:  static WorkItemStore workItemStore;
  24:  static TfsTeamProjectCollection tfsTeamProjectCollection;
  25:  static TfsTeamProjectCollection GetTeamProjectCollection()
  26:  {
  27:      var tfsTeamProjectCollection = new TfsTeamProjectCollection(
  28:         new Uri(TfsServer),
  29:     new System.Net.NetworkCredential(TfsUserName, TfsPassword));
  30:      tfsTeamProjectCollection.EnsureAuthenticated();
  32:      return tfsTeamProjectCollection;
  33:  }
  35:  static void Main(string[] args)
  36:  {
  37:      tfsTeamProjectCollection = GetTeamProjectCollection();
  39:      //First we get the version control server.
  40:      var versionControl = tfsTeamProjectCollection.GetService<VersionControlServer>();
  42:      workItemStore = new WorkItemStore(tfsTeamProjectCollection);
  44:      // Second we get a list of merge candidates between our two branches (very simple)
  45:      var mergeCandidates = 
  46:          versionControl.GetMergeCandidates(FromBranch, ToBranch, RecursionType.Full);
  48:      //Thirdly we get a list of workitems from our changesets (using some recursion)
  49:      var workItems = GetWorkItemsForChangesets(mergeCandidates);
  51:      //And last we output these all to the screen
  52:      foreach (var workItem in workItems)
  53:      {
  54:          Console.WriteLine(string.Format("{0} {1}", workItem.Id, workItem.Title));
  55:      }
  57:      Console.WriteLine("Complete");
  58:      Console.ReadLine();
  59:  }

We get our TFS collection first and then pull out our change sets from TFS using the VersonControlServer as you can see TFS gives us this functionality straight out of the box.

Next we go and get our workitems from TFS by iterating through our merge candidates and then workitems. If we find a PBI at this level we store them. If we find Tasks we then look inside them for more PBI’s

   1:  //In the example below I have deliberately not used LINQ statements so you can see what is happening more clearly.
   2:  // These lines of code can quite easily be condensed
   3:  static IEnumerable<WorkItem> GetWorkItemsForChangesets(IEnumerable<MergeCandidate> mergeCand)
   4:  {
   5:      var workItems = new List<WorkItem>();
   7:      foreach (var itemMerge in mergeCand)
   8:      {
   9:          var changeSet = itemMerge.Changeset;
  11:          var workItemsCollection = changeSet.WorkItems;
  13:          foreach (WorkItem item in workItemsCollection)
  14:          {
  15:              if (ProductBackLogItem == item.Type.Name)
  16:              {
  17:                  if (!WorkItemCache.Contains(item.Id))
  18:                  {
  19:                      WorkItemCache.Add(item.Id);
  20:                      workItems.Add(item);
  21:                  }
  22:              }
  24:              if (item.WorkItemLinks.Count > 0 && SprintBacklogTask == item.Type.Name)
  25:                  {
  26:                      WorkItemCache.Add(item.Id);
  27:                      var collectedWorkItems = GetProductBacklogItems(item);
  29:                      if (collectedWorkItems != null && collectedWorkItems.Count > 0)
  30:                      {
  31:                          workItems.AddRange(collectedWorkItems);
  32:                      }
  33:                  }
  36:          }
  37:      }
  39:      return workItems;
  40:  }

The code below will take a Task item and check its links for a parent item such as a PBI.

   1:  static List<WorkItem> GetProductBacklogItems(WorkItem workItem)
   2:  {
   3:      var workItems = new List<WorkItem>();
   5:      foreach (WorkItemLink workItemLinks in workItem.WorkItemLinks)
   6:      {
   7:          //We only want parent items so we look for "Implements"
   8:          if (workItemLinks.TargetId > 0 && (workItemLinks.LinkTypeEnd.Name == "Implements"))
   9:          {
  10:              var tempWorkItem = workItemStore.GetWorkItem(workItemLinks.TargetId);
  12:              if (ProductBackLogItem == tempWorkItem.Type.Name)
  13:              {
  14:                  if (!WorkItemCache.Contains(tempWorkItem.Id))
  15:                  {
  16:                      WorkItemCache.Add(tempWorkItem.Id);
  17:                      workItems.Add(tempWorkItem);
  18:                  }
  19:              }
  20:          }
  21:      }
  23:      return workItems;
  24:  }

That's all there is to it. I’m pretty sure the code can be refactored to work better. I would state to be careful when using the code above. Searching through workitems can take a lot of time so if you do end up using it try it out on a test TFS or use your code within a debugger so can see what its doing.

Tags: TFS

Tuesday, January 29, 2013 1:03:54 PM (GMT Standard Time, UTC+00:00)  #    Comments [0]

# Monday, January 28, 2013

I was recently brought into a client site where they had made use of PSAKE to handle their build process. The build would be kicked off from the traditional Workflow in TFS using an Invoke Process. Everything was working perfectly until they spotted that when the build failed there was no way of viewing which unit tests had failed from within TFS. In short PowerShell was giving precious little to the TFS summary view.

The question was how could we get that rich logging information you got in the build summary when doing a traditional build using Workflow? Setting up a traditional build and observing how MSBUILD is called from TFS starts to shed some light on the situation

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe /nologo /noconsolelogger "C:\Builds\1\Scratch\Test Build\Sources\user\Test\Build.proj" /m:1 /fl /p:SkipInvalidConfigurations=true  /p:OutDir="C:\Builds\1\Scratch\Test Build\Binaries\\" /p:VCBuildOverride="C:\Builds\1\Scratch\Test Build\Sources\user\Test\Build.proj.vsprops" /dl:WorkflowCentralLogger,"C:\Program Files\Microsoft Team Foundation Server 2010\Tools\Microsoft.TeamFoundation.Build.Server.Logger.dll";"Verbosity=Normal;BuildUri=vstfs:///Build/Build/111;InformationNodeId=6570;
http://mytfshost:8080/tfs/Test%20Collection;"*WorkflowForwardingLogger,"C:\Program Files\Microsoft Team Foundation Server 2010\Tools\Microsoft.TeamFoundation.Build.Server.Logger.dll";"Verbosity=Normal;"


In the above example I have highlighted the section I discovered is responsible for the summary view you usually see when kicking off a build from TFS. I discovered this with a bit of guesswork and some reflector usage to see what was going on inside MSBUILD. Googling for the WorkflowCentralLogger gives precious little back about how it works and more about the errors people have encountered with it.

Getting to the solution
You will be forgiven for thinking the answer to the problem is just adding the missing WorkflowCentralLogger switch (with arguments) to your MSBUILD command line in PowerShell/PSAKE. Sadly its not that simple. See the InformationNodeId in the above command line? This appears to tell the WorkFlowCentralLogger where it needs to append its logging information. Passing it into the Invoke Process was my first thought, the problem is you’re not going to find anything that will give it to you, I wasn’t able to find it anywhere.

So how do you get it to work then?
The answer is, you need to build a Custom Workflow Activity. A custom workflow activity will have access to the current Context. To use this you need to inherit the class “CodeActivity”. Its up to you how you use this Custom Workflow Activity, you have one of two ways.

  • Place it above the Invoke Process in your workflow, get the InformationNodeId and pass this as an OutArgument to the Invoke Process below it (not tested fully)
  • Or invoke Powershell from within the Custom Activity using a runspace and pass it the code context. (fully tested)
   3:  namespace MyWorkflowActivities
   4:  {
   5:      using System;
   6:      using System.Collections.Generic;
   7:      using System.Linq;
   8:      using System.Text;
   9:      using System.Collections.ObjectModel;
  10:      using System.Management.Automation;
  11:      using System.Management.Automation.Runspaces;
  12:      using System.IO;
  13:      using System.Activities;
  14:      using System.Collections;
  15:      using System.Globalization;
  17:      using Microsoft.TeamFoundation.Build.Client;
  18:      using Microsoft.TeamFoundation.Build.Workflow.Activities;
  19:      using Microsoft.TeamFoundation.Build.Workflow.Services;
  21:      public OutArgument<string> InformationNodeIdOut { get; set; }
  23:      [BuildActivity(HostEnvironmentOption.All)]
  24:      public sealed class GetInformationNodeId : CodeActivity
  25:      {
  26:          protected override void Execute(CodeActivityContext context)
  27:          {
  29:              context.TrackBuildMessage("Getting the Information Node Id", BuildMessageImportance.Low);
  30:              IActivityTracking activityTracking = context.GetExtension<IBuildLoggingExtension>().GetActivityTracking((ActivityContext) context);
  31:              string informationNodeId = activityTracking.Node.Id.ToString("D", (IFormatProvider)CultureInfo.InvariantCulture);
  33:              context.SetValue<string>(this.InformationNodeIdOut, informationNodeId);
  34:          }
  35:      }
  37:  }

The code above illustrates the first solution. Its a lot simpler and you’ll have to pass that node id to MSBUILD when you construct its command line in PowerShell. Line 30 and 31 is where all the magic takes place, I managed to find this line using reflector in MSBUILD. If you have never written a custom activity before Ewald Hofman has a short summary of one here

The diagram below illustrates where GetInformationNodeId (code above) sits just above the InvokeProcess which calls PowerShell.



The second solution, which I actually went with is slightly more complex and I’ll blog about how I did that in another article. You might be wondering what are the immediate benefits of one over the other? The beauty of going with the second solution is you can make use of the code activity context within your PowerShell scripts. So for example instead of writing your PowerShell events out to the host you could wrap that call in context.TrackBuildMessage (as illustrated on line 29 above).

I’d be interested to hear about other peoples experiences.


Monday, January 28, 2013 3:51:49 PM (GMT Standard Time, UTC+00:00)  #    Comments [0]