Team Foundation Server has allowed you to modify your Work Item Type Definitions since the first version of TFS. (Side note: this is not the case with the Team Foundation Service, but the team hopes to enable that at some point in the future. At the moment, limiting the customization allows them to innovate the features in the Service at a faster pace without having to worry too much about everybody's customizations.)
The fundamentals for modifying Work Item Types are documented in the following places:
In this post, I'm going to show you the tools and process that I personally use for customizing work item types.
Prerequisites / Tools
- Real (Production) TFS server / project
- Test (Staging) TFS server / project
- ExportWITDs.cmd - A batch file (included below) that uses the 'witadmin.exe exportwitd' command
- ImportWITDs.cmd
- Visual Studio, XML editor with IntelliSense
- Checkin.cmd - A batch file that uses tf.exe to prompt for a comment and check-in current changes.
- Team Foundation Server Power Tools - Process Editor
Workflow
When I'm working with a customer and doing a series of process template or work item type customization, this is the workflow that I follow:
- Run a script to export all Work Item Type definitions to my local machine
- Check-in a copy of the definitions to source control, so that we have a baseline to work from and revert back to
- Edit the XML definitions in Visual Studio as XML, with IntelliSense (see below)
- Run a script to import the definition to my Test project
- Verify the changes in a second copy of Visual Studio
- Check-in the changes
- Run a script to import the definition to my Production project
Step 1 - Export all work item types
The following script exports a list of the work item type names to a temporary file, then uses that list to export each of the work item types to a separate file in the current directory. It needs to be run from a Visual Studio Command Prompt, or you need to add ‘C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\ide’ to your PATH environment variable.
ExportWITDs.cmd:
SET collection="http://tfs-server:8080/tfs/DefaultCollection"
SET project="Project XYZ"
witadmin listwitd /collection:%collection% /p:%project% > %temp%\witd.txt
:: Remove quotes from project name
SET _string=###%project%###
SET _string=%_string:"###=%
SET _string=%_string:###"=%
SET _string=%_string:###=%
for /F "delims=" %%a in (%temp%\witd.txt) do witadmin exportwitd /collection:%collection% /p:%project% /n:"%%a" /f:"%3_%_string%_%%a.xml"
Step 2 - Check-in a copy
There’s no script for this step, since it’s a one-time thing. Just use Visual Studio, or ‘tf add . /R’ followed by ‘tf checkin . /R’
Step 3 - Open with XML Editor
See my previous blog post on how I enable IntelliSense for editing work item types as XML.
Step 4 - Import the changes to Test
Importing the changes is relatively straightforward. When I am rapidly iterating on a Work Item Type design, I like to create a 'ImportWITDs.cmd' batch file that imports everything that I'm currently working on. Then I can just leave a command prompt open and run it whenever I feel like it.
Now, for the seasoned witadmin pros, you'll know that there's also a '/v' option that allows you to validate the changes before you actually upload them to the server. In my experience, this is a waste of time - two reasons:
- If the XML is invalid, then it's going to fail if you try and upload it without validating first.
- The validation process doesn't validate everything - it misses some things. (I forget the specific cases, but I think it was something like fields that already exist or something like that).
So because of these two reasons and coupled with the fact that I'm also uploading to a test server first - I skip the '/v' validation step and try the import directly.
ImportWITDs.cmd:
SET collection="http://tfs-server:8080/tfs/DefaultCollection"
SET project="Project XYZ"
witadmin importwitd /collection:%collection% /p:%project% /f:"_DefaultCollection_Task.xml"
witadmin importwitd /collection:%collection% /p:%project% /f:"_DefaultCollection_Bug.xml"
witadmin importwitd /collection:%collection% /p:%project% /f:"_DefaultCollection_Issue.xml"
witadmin importwitd /collection:%collection% /p:%project% /f:"_DefaultCollection_Shared Steps.xml"
witadmin importwitd /collection:%collection% /p:%project% /f:"_DefaultCollection_Test Case.xml"
Step 5 - Verify the changes
Once I've run the ImportWITDs.cmd script, and it completes without any errors - then it's time to verify the changes. To do this, I normally have a second copy of Visual Studio open.
Before hitting 'Refresh' in Team Explorer, it's important to close all existing Work Item tabs. As having an open query or work item, can cause the metadata not to be reloaded correctly – then you start to wonder whether your changes were uploaded successfully or not.
Once everything is closed, hit the 'Refresh' button at the top of Team Explorer. Then go ahead and open a New Work Item form for the type that you have just modified.
Step 6 - Check-in the changes
If you have verified the changes and everything looks great - it's a good idea to check the XML in to source control. This gives you a point that you can roll-back to in the future. It also helps your successor understand what changes have been made to the work item types and why they were made.
After checking in the changes, we also check-out all the files again. (This is not strictly necessary if you are using Visual Studio 2012 and Local Workspaces, since the files will be read-write on disk and any changes will be detected anyway.)
Checkin.cmd:
@echo off
SET /P comment="Checkin comment?"
tf checkin . /r /noprompt /comment:"%comment%"
tf edit . /R
Step 7 - Import the changes to Production
Once we've checked-in a copy of our changes, it's time to upload the Work Item Type changes to the Production team project. If you're making the changes on behalf of a customer, then you would have them review the changes on your Test system first.
Since I normally iterate on a set of changes a few times and upload to Production once at the end, I usually just modify the Server/Collection/Project settings in ImportWITDs.cmd and use that, rather than creating a separate batch file.
Other Tools
Although they are not part of the "normal" workflow, there are some other tools that I have used in the past for special situations.
TFS Team Project Manager
I can’t recommend this tool from Jelle Druyts highly enough for doing what I call “Bulk Administration” tasks in TFS. It lets you easily take a set of Work Item Types and upload them to all projects in your project collection. It also lets you bulk edit build definitions, build process templates, fields, source control settings and more.
![image image]()
ExportWITDSorted.exe
This is a little tool that I wrote for myself. The use case that I wrote it for was when you a working with heavily customised work item types that you don't have the original XML for.
Although the way that you modify work item types is all in XML - that is not how the work item types are defined in the TFS database. When you tell TFS to import your work item type XML file, it shreds the XML, parses out all the fields, layouts, transitions, etc and puts them in separate SQL tables. When you tell TFS to export a work item type as XML, it does the opposite. The ordering of the elements in the XML is basically the ordering of the rows from the database. No sorting.
If you are trying to do a diff of different work item type XML files, this can be pretty frustrating. Of course you can go and get a diff tool that understands XML semantics, but Visual Studio can't do this for you.
This tool I wrote uses the same APIs that 'witadmin exportwitd' uses to get an export of the work item type, but then it iterates through the XML elements and sorts them by the 'name' attribute. This makes it a little easier to diff with a 'dumb' text-diff tool like Visual Studio or WinMerge.
using System;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Xml;
using System.Xml.XPath;
namespace ExportWitdSorted
{
class Program
{
static void Main(string[] args)
{
if (args == null || args.Length != 4)
{
Console.WriteLine("Usage: ExportWitdSorted.exe <collection url> <project> <work item type> <outputfile>");
Console.WriteLine("Example: ExportWitdSorted.exe http://tfsserver:8080/Collection MyProject \"My Bug\" mybug.xml");
Environment.Exit(1);
}
// Connect to TFS
TfsTeamProjectCollection tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(args[0]));
WorkItemStore wis = tpc.GetService<WorkItemStore>();
Project project = wis.Projects[args[1]];
WorkItemType type = project.WorkItemTypes[args[2]];
// Export the work item definition to an XmlDocument
XmlDocument originalDoc = type.Export(false);
// Create a copy of the definition and remove all the <FIELD> nodes so that we can replace them with a sorted list
XmlDocument sortedDoc = new XmlDocument();
sortedDoc.LoadXml(originalDoc.OuterXml);
sortedDoc.SelectSingleNode("//FIELDS").RemoveAll();
// Get the nodes from the original document and sort them
XmlNode node = originalDoc.SelectSingleNode("//FIELDS");
XPathNavigator navigator = node.CreateNavigator();
XPathExpression selectExpression = navigator.Compile("FIELD/@name");
selectExpression.AddSort(".", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Text);
XPathNodeIterator nodeIterator = navigator.Select(selectExpression);
// Import the sorted nodes into the new document
while (nodeIterator.MoveNext())
{
XmlNode fieldNode = originalDoc.SelectSingleNode("//FIELD[@name='" + nodeIterator.Current.Value + "']");
XmlNode importedFieldNode = sortedDoc.ImportNode(fieldNode, true);
sortedDoc.SelectSingleNode("//FIELDS").AppendChild(importedFieldNode);
}
sortedDoc.Save(args[3]);
}
}
}