Bulk update of TFS Areas with ICommonStructureService

I recently had to create an area tree, in a TFS project, with  large number (500+) of nodes.  I was not looking forward to doing this manually.  I had the area tree that I needed to create in a comma delimited (CSV) file so I looked around online to see if someone had a utility I could use to import the areas paths in a csv file and add these as new TFS areas in a project. 

The TFS community online is so great.  But this time I could not find anything really close to what I was looking for.  I list some good blog posts at the end of this post that got me started using  ICommonStructureService. 

With these leads I created the below command line utility that will consume a file with your areas and add them as areas in your particular TFS project.

You should be able to cut and paste the code below being sure to reference the following assemblies as well.

Microsoft.TeamFoundation.dll
Microsoft.TeamFoundation.Client.dll
Microsoft.TeamFoundation.Common.dll
Microsoft.TeamFoundation.Server.dll
Microsoft.TeamFoundation.WorkItemTracking.Client.dll

Input CSV format example

Parent,Area1,SubArea1
Parent,Area1,SubArea2
Parent,Area2,SubArea1

Sample syntax

TFSAreaTool.exe [TFS Server] [TFS Project] [input csv file]

example: TFSAreaTool.exe https://TFSRTM08:8080 sandbox "Feature Areas.csv"

Code

using System;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;

namespace TFSAreaTool
{
  class Program
  {
    static int Main(string[] args)
    {
      //check and set required parameters
      if (args.Length < 3)
      {
        showSyntax();
        return 1;
      }
      string tfsServer = args[0].Trim();
      string tfsProject = args[1].Trim();
      string inputFile = args[2].Trim();
      string line;

      //connect to TFS
      TeamFoundationServer tfs = null;
      try
      {
        Console.WriteLine("Connecting to " + tfsServer);
        tfs = TeamFoundationServerFactory.GetServer(tfsServer);
      }
      catch (Exception e)
      {
        Console.WriteLine("Error: " + e.Message);
        return 2;
      }

      //use ICommonStructureService to access area nodes
      ICommonStructureService m_css = null;
      try
      {
        Console.WriteLine("Connecting to structure service");
        m_css = (ICommonStructureService)tfs.GetService(typeof(ICommonStructureService));
      }
      catch (Exception e)
      {
        Console.WriteLine("Error: " + e.Message);
        return 3;
      }
      //iterate through input file with list of areas
      StreamReader sr = null ;
      try
      {
        Console.WriteLine("Reading input file " + inputFile);
        sr = new StreamReader(inputFile);
      }
      catch (Exception e)
      {
        Console.WriteLine("Error: " + e.Message);
        return 4;
      }

      while (!sr.EndOfStream)
      {
        line = sr.ReadLine();
        string[] areas  = Regex.Split(line, ",");
        //start with top level area path as parent
        string parentPath = tfsProject + "\\area" ;

        //iterate through areas listed in input file
        foreach (string area in areas)
        {
          createArea(area, parentPath, m_css);

          //parent path for next iteration is the node that we just created
          parentPath = parentPath + "\\" + area;
        }
        parentPath = null;

      }
      sr.Close();
      m_css = null;
      tfs = null;
      return 0;
    }
    static void createArea(string areaNode, string parentPath, ICommonStructureService m_css)
    {
      String node = null;
      try
      {
        Console.WriteLine("Create Area:   " + parentPath + "\\" + areaNode) ;
        node = m_css.CreateNode(areaNode, m_css.GetNodeFromPath(parentPath).Uri);
      }
      catch
      {
        //any duplicate area paths will catch here.  May be good to post a warning
      }

    }
    static void showSyntax()
    {
      Console.WriteLine("TFSAreaTool creates TFS Area tree based on CSV formatted input file.");
      Console.WriteLine();
      Console.WriteLine("Input file example:");
      Console.WriteLine("Parent,area1.0,area1.1,area1.11");
      Console.WriteLine("Parent,area1.0,area1.1,area1.12");
      Console.WriteLine("Parent,area2.0,area2.1,area2.11");
      Console.WriteLine();
      Console.WriteLine("Syntax: -[TFS Server] [project] [areas list file]");
      Console.WriteLine();
      Console.WriteLine("Example: tfsareatool.exe https://myTFSServer:8080 SANDBOX areas.csv");
    }
  }
}

Caveats

The catch block in the createArea method catches attempts to create areas that have already been created or fail to be created.  Some additional handling here would be helpful.

Also note, if an area is created already this catch block with activate and your existing area will persist.  This means you can run this script multiple times if needed to add on to your area tree without losing any existing area information.

Links to related blog posts

https://blog.benday.com/archive/2007/07/24/23153.aspx

https://blogs.microsoft.co.il/blogs/shair/archive/2009/01/30/tfs-api-part-10-add-area-iteration-programmatically.aspx

https://blogs.msdn.com/bharry/archive/2006/08/28/728333.aspx

Conclusion

I hope you find this saved you a bit of time when doing a bulk import of TFS project areas.  Enjoy!