Team Foundation Server 2012 Build Global List Problems
- by Bob Hardister
My experience with the upgrade and use of TFS 2012 has been very positive. I did come across a couple of issues recently that tripped things up for a while. ISSUE 1 The first issue is that 2012 prior to Update 1 published an invalid build list item value to the collection global list. In 2010, the build global list, list item value syntax is an underscore between the build definition and the build number. In the 2012 RTM this underscore was replaced with a backslash, which is invalid. Specifically, an upload of the global list fails when the backslash is followed at some point by a period. The error when using the API is: <detail ExceptionMessage="TF26204: The account you entered is not recognized. Contact your Team Foundation Server administrator to add your account." BaseExceptionName="Microsoft.TeamFoundation.WorkItemTracking.Server.ValidationException"><details id="600019" http://schemas.microsoft.com/TeamFoundation/2005/06/WorkItemTracking/faultdetail/03"http://schemas.microsoft.com/TeamFoundation/2005/06/WorkItemTracking/faultdetail/03" /></detail> when uploading the global list via the process editor the error is: This issue is corrected in Update1 as the backslash is changed to a forward slash. ISSUE 2 The second issue is that when upgrading from 2010 to 2012, the builds in 2010 are not published to the 2012 global list. After the upgrade the 2012 global lists doesn’t have any builds and only builds run in 2012 are published to the global list. This was reported to the MSDN forums and Connect. To correct this I wrote a utility to pull all the builds and recreate the builds global list for each project in each collection. This is a console application with a program.cs, a globallists.cs and a app.config (not published here). The utility connects to TFS 2012, loops through the collections or a target collection as specified in the app.config. Then loops through the projects, the build definitions, and builds. It creates a global list for each project if that project has at least one build. Then it imports the new list to TFS. Here’s the code for program and globalists classes. Program.CS using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using System.IO;
using System.Xml;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Diagnostics;
using Utilities;
using System.Configuration;
namespace TFSProjectUpdater_CLC
{
class Program
{
static void Main(string[] args)
{
DateTime temp_d = System.DateTime.Now;
string logName = temp_d.ToShortDateString();
logName = logName.Replace("/", "_");
logName = logName + "_" + temp_d.TimeOfDay;
logName = logName.Replace(":", ".");
logName = "TFSGlobalListBuildsUpdater_" + logName + ".log";
Trace.Listeners.Add(new TextWriterTraceListener(Path.Combine(ConfigurationManager.AppSettings["logLocation"], logName)));
Trace.AutoFlush = true;
Trace.WriteLine("Start:" + DateTime.Now.ToString());
Console.WriteLine("Start:" + DateTime.Now.ToString());
string tfsServer = ConfigurationManager.AppSettings["TargetTFS"].ToString();
GlobalLists gl = new GlobalLists();
//replace this with the URL to your TFS instance.
Uri tfsUri = new Uri("https://" + tfsServer + "/tfs");
//bool foundLite = false;
TfsConfigurationServer config = new TfsConfigurationServer(tfsUri, new UICredentialsProvider());
config.EnsureAuthenticated();
ITeamProjectCollectionService collectionService = config.GetService<ITeamProjectCollectionService>();
IList<TeamProjectCollection> collections = collectionService.GetCollections().OrderBy(collection => collection.Name.ToString()).ToList();
//target Collection
string targetCollection = ConfigurationManager.AppSettings["targetCollection"];
foreach (TeamProjectCollection coll in collections)
{
if (targetCollection.Equals(string.Empty))
{
if (!coll.Name.Equals("TFS Archive") && !coll.Name.Equals("DefaultCol") && !coll.Name.Equals("Team Project Template Gallery"))
{
doWork(coll, tfsServer);
}
}
else
{
if (coll.Name.Equals(targetCollection))
{
doWork(coll, tfsServer);
}
}
}
Trace.WriteLine("Finished:" + DateTime.Now.ToString());
Console.WriteLine("Finished:" + DateTime.Now.ToString());
if (System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine("\nHit any key to exit...");
Console.ReadKey();
}
Trace.Close();
}
static void doWork(TeamProjectCollection coll, string tfsServer)
{
GlobalLists gl = new GlobalLists();
//target Collection
string targetProject = ConfigurationManager.AppSettings["targetProject"];
Trace.WriteLine("Collection: " + coll.Name);
Uri u = new Uri("https://" + tfsServer + "/tfs/" + coll.Name.ToString());
TfsTeamProjectCollection c = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(u);
ICommonStructureService icss = c.GetService<ICommonStructureService>();
try
{
Trace.WriteLine("\tChecking Collection Global Lists.");
gl.RebuildBuildGlobalLists(c);
}
catch (Exception ex)
{
Console.WriteLine("Exception! :" + coll.Name);
}
}
}
}
GlobalLists.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Build.Client;
using System.Configuration;
using System.Xml;
using System.Xml.Linq;
using System.Diagnostics;
namespace Utilities
{
public class GlobalLists
{
string GL_NewList = @"<gl:GLOBALLISTS xmlns:gl=""http://schemas.microsoft.com/VisualStudio/2005/workitemtracking/globallists"">
<GLOBALLIST>
</GLOBALLIST>
</gl:GLOBALLISTS>";
public void RebuildBuildGlobalLists(TfsTeamProjectCollection _tfs)
{
WorkItemStore wis = new WorkItemStore(_tfs);
//export the current globals lists file for the collection to save as a backup
XmlDocument globalListsFile = wis.ExportGlobalLists();
globalListsFile.Save(@"c:\temp\" + _tfs.Name.Replace("\\", "_") + "_backupGlobalList.xml");
LogExportCurrentCollectionGlobalListsAsBackup(_tfs);
//Build a new global build list from each build definition within each team project
IBuildServer buildServer = _tfs.GetService<IBuildServer>();
foreach (Project p in wis.Projects)
{
XmlDocument newProjectGlobalList = new XmlDocument();
newProjectGlobalList.LoadXml(GL_NewList);
LogInstanciateNewProjectBuildGlobalList(_tfs, p);
BuildNewProjectBuildGlobalList(_tfs, wis, newProjectGlobalList, buildServer, p);
LogEndOfProject(_tfs, p);
}
}
// Private Methods
private static void BuildNewProjectBuildGlobalList(TfsTeamProjectCollection _tfs, WorkItemStore wis, XmlDocument newProjectGlobalList, IBuildServer buildServer, Project p)
{
//locate the template node
XmlNamespaceManager nsmgr = new XmlNamespaceManager(newProjectGlobalList.NameTable);
nsmgr.AddNamespace("gl", "http://schemas.microsoft.com/VisualStudio/2005/workitemtracking/globallists");
XmlNode node = newProjectGlobalList.SelectSingleNode("//gl:GLOBALLISTS/GLOBALLIST", nsmgr);
LogLocatedGlobalListNode(_tfs, p);
//add the name attribute for the project build global list
XmlElement buildListNode = (XmlElement)node;
buildListNode.SetAttribute("name", "Builds - " + p.Name);
LogAddedBuildNodeName(_tfs, p);
//add new builds to the team project build global list
bool buildsExist = false;
if (AddNewBuilds(_tfs, newProjectGlobalList, buildServer, p, node, buildsExist))
{
//import the new build global list for each project that has builds
newProjectGlobalList.Save(@"c:\temp\" + _tfs.Name.Replace("\\", "_") + "_" + p.Name + "_" + "newGlobalList.xml"); //write out temp copy of the global list file to be imported
LogImportReady(_tfs, p);
wis.ImportGlobalLists(newProjectGlobalList.InnerXml);
LogImportComplete(_tfs, p);
}
}
private static bool AddNewBuilds(TfsTeamProjectCollection _tfs, XmlDocument newProjectGlobalList, IBuildServer buildServer, Project p, XmlNode node, bool buildsExist)
{
var buildDefinitions = buildServer.QueryBuildDefinitions(p.Name);
foreach (var buildDefinition in buildDefinitions)
{
var builds = buildDefinition.QueryBuilds();
foreach (var build in builds)
{
//insert the builds into the current build list node in the correct 2012 format
buildsExist = true;
XmlElement listItem = newProjectGlobalList.CreateElement("LISTITEM");
listItem.SetAttribute("value", buildDefinition.Name + "/" + build.BuildNumber.ToString().Replace(buildDefinition.Name + "_", ""));
node.AppendChild(listItem);
}
}
if (buildsExist) LogBuildListCreated(_tfs, p); else LogNoBuildsInProject(_tfs, p); return buildsExist;
}
// Logging Methods
private static void LogExportCurrentCollectionGlobalListsAsBackup(TfsTeamProjectCollection _tfs)
{
Trace.WriteLine("\tExported Global List for " + _tfs.Name + " collection.");
Console.WriteLine("\tExported Global List for " + _tfs.Name + " collection.");
}
private void LogInstanciateNewProjectBuildGlobalList(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tInstanciated the new build global list for project " + p.Name + " in the " + _tfs.Name + " collection.");
Console.WriteLine("\t\tInstanciated the new build global list for project \n\t\t\t" + p.Name + " in the \n\t\t\t" + _tfs.Name + " collection.");
}
private static void LogLocatedGlobalListNode(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tLocated the build global list node for project " + p.Name + " in the " + _tfs.Name + " collection.");
Console.WriteLine("\t\tLocated the build global list node for project \n\t\t\t" + p.Name + " in the \n\t\t\t" + _tfs.Name + " collection.");
}
private static void LogAddedBuildNodeName(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tAdded the name attribute to the build global list for project " + p.Name + " in the " + _tfs.Name + " collection.");
Console.WriteLine("\t\tAdded the name attribute to the build global list for project \n\t\t\t" + p.Name + " in the \n\t\t\t" + _tfs.Name + " collection.");
}
private static void LogBuildListCreated(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tAdded all builds into the " + "Builds - " + p.Name + " list in the " + _tfs.Name + " collection.");
Console.WriteLine("\t\tAdded all builds into the " + "Builds - \n\t\t\t" + p.Name + " list in the \n\t\t\t" + _tfs.Name + " collection.");
}
private static void LogNoBuildsInProject(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tNo builds found for project " + p.Name + " in the " + _tfs.Name + " collection.");
Console.WriteLine("\t\tNo builds found for project " + p.Name + " \n\t\t\tin the " + _tfs.Name + " collection.");
}
private void LogEndOfProject(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tEND OF PROJECT " + p.Name);
Trace.WriteLine(" ");
Console.WriteLine("\t\tEND OF PROJECT " + p.Name);
Console.WriteLine();
}
private static void LogImportReady(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tReady to import the build global list for project " + p.Name + " to the " + _tfs.Name + " collection.");
Console.WriteLine("\t\tReady to import the build global list for project \n\t\t\t" + p.Name + " to the \n\t\t\t" + _tfs.Name + " collection.");
}
private static void LogImportComplete(TfsTeamProjectCollection _tfs, Project p)
{
Trace.WriteLine("\t\tImport of the build global list for project " + p.Name + " to the " + _tfs.Name + " collection completed.");
Console.WriteLine("\t\tImport of the build global list for project \n\t\t\t" + p.Name + " to the \n\t\t\t" + _tfs.Name + " collection completed.");
}
}
}