A lot of people have asked "How can I copy work items between Team Projects?".  Well, the answer is complex.  This blog post is to give an example of one (of many) ways to address this situation.

Background

Visual Studio Team Foundation Server is an advanced enterprise lifecyle management tool.  The complexities start when a single team project can reference other team projects: source control items related in other team projects, linked changesets to a work item spanning team projects, other work items, etc.   Due to these complexities, just simply to copy all the items related to work items is difficult due to the rich amount of functionality in TFS.  There is no built-in tool available in TFS v1.0 to copy work items between team projects.   The following addresses some of the areas of the work items to allow for a shallow copy to another server/team project. 

Scenario 

A scenario where you might want to copy work items from one server ot another is when prototyping new guidance.   After the guidance change is fully tested and ready to move to a new server from your poof-of-concept environment.  Your project that help test the guidance change may need to continue and be moved to a production development environment.   Moving a complete team project and all of it artifacts are not the focus of this article, however I will address the work item part of the scenario.

Scope of this Article

The article represent an approach and not necessarily a recommendation or best practice.  Additionally, there are ways to use MS Project and MS Excel to export then publish to a different server and team project.  With the Project/Excel export method, links and attachments will be lost.   This article is one way to address attachments and at least an approach toward hyperlinks using the TFS API.

Shallow copy includes

  • Data fields (except areas, iterations,changed by, modified by - see step below for details)
  • Attachments
  • Hyperlinks

Room for improvements

  • Changeset Links
  • Work Item Links
  • Source Control Links

Step 1: Need location for retrieving source work item attachments

In order to push attachments into a new server you must first extract the existing attachments.  Since attachments are stored in the database and you need them another you will have to extract them and make a copy locally before saving them to the destination server's work item attachmetn repository.

Step 2: Open Source Server and Team Project  to retrieve a list of all the work items

The following sample code is how to retrieve the id for all work items from a  server named TFSRTM and a source project named SourceProject

TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer("http://TFSRTM:8080");
WorkItemStore wiStore = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
string QueryValue = string.Format(CultureInfo.CurrentUICulture,"Select id from workitems WHERE [Team Project] = '{0}' ORDER BY id ASC", project);
WorkItemCollection SourceWorkItemCollection  = wiStore.Query("Select id from workitems WHERE [Team Project] = 'SourceProject' );

Step 3:  Open Destination Server and Team Project

Sample source assumes TFSDEST server with project name NewProject.

TeamFoundationServer tfsDest = TeamFoundationServerFactory.GetServer("http://TFSDEST:8080");
WorkItemStore wiDestStore = (WorkItemStore)tfsDest.GetService(typeof(WorkItemStore));
Project destProject = wiDestStore.Projects["NewProject"];

Step 4: Loop through each work item from source

foreach (WorkItem SourceWorkItem in SourceWorkItemCollection)
{
<Copy data fields>
<Copy attachments>
<Copy hyperlinks>
}

Step 5: Copy data fields

This step effectively makes a copy of the source work item data items and places it in the destination work item.   There are many approaches to copying field data.

WorkItemType destWIT = destProject.WorkItemTypes[SourceWorkItem.Type.Name];
WorkItem destWI = new WorkItem(destWIT);
destWI.Open();
foreach (Field fld in SourceWorkItem.Fields)
{
  if (destWI.Fields[fld.Name].IsEditable)
  {
    switch (fld.Name)
    {
     
case "Iteration Path":
      case "IterationID":
      case "Area Path":
     
case "AreaID":
     
case "Changed By":
     
case "Changed Date":
     
case "Activated By":
     
case "Activated Date":
      case "Assigned To":
      case "Assigned Date":
        // need to determine your needs on how to handle these scenarios like mapping, defaulting to values, etc
        break;
      default:
        destWI.Fields[fld.Name].Value = fld.Value;
        if (!destWI.Fields[fld.Name].IsValid)
          throw new SystemException()
        break;
      }
}

Step 6: Copy file attachments

This step requires you to:

  1. Loop through each source attachment
  2. Retrieve source binary from Uri and store to temp location
  3. Fixup file information to what is stored in database (i.e. created time, modified time, etc)
  4. Create a destination attachment with reference to local temp binary
  5. Add it to destination attachment

Most of the steps are straight forward, however below is a snippet of step 3 to give you a better idea of what is needed.  In the snippet below, attachmentItem is source attachment reference and TempFile is new temporary file and the downloaded version of the Uri in attachmentItem.

// Reset all attributes to original source attachment
FileInfo fi = new FileInfo(TempFile);
fi.CreationTimeUtc = attachmentItem.CreationTimeUtc;
fi.LastWriteTimeUtc = attachmentItem.LastWriteTimeUtc;

Step 7: Copy the hyperlinks

Here is a code sample for an approach you could use to cycle through the links on a work item and copy at least hyperlinks.

if (SourceWorkItem.HyperLinkCount > 0)
{
  foreach (Link linkItem in SourceWorkItem.Links)
  {
   
// Only add hyperlinks
    if (linkItem.BaseType == BaseLinkType.Hyperlink) destWI.Links.Add(linkItem);
  }
}

Step 8: Save the destination work item

The final step is to save the destination work item.  This could be done by calling .Save on the work item or creating a collection (if a small number of total work items) and doing a .BatchSave on the collection.

Conclusion

I hope this give some insight on how you might tackle the problem.  Although this gives you a sample of how to shallow copy of some work item parts, your scenario may focus on other areas and require a different approach.  If anyone has any suggestions, feel free to leave a comment.