How to modify read only TFS fields with WorkItemServer.Update

Sometimes you want to update a read only system field in TFS, such as System.CreatedDate or System.ResolvedDate. There is no way to do that with TFS 2010 by using the UI or the object model because both of those methods execute validation rules and you can’t disable them. I heard that you can bypass them in TFS 2012 using the object model, but with TFS 2010 you have to use the WorkItemServer.Update method and pass in the values in an xml package with BypassRules set to true.

Most people will first try to set the property directly using the object model like this:

item.CreatedDate = date;

But you get the error: “The property ‘Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem.CreatedDate’ has no setter” OR “Property or indexer ‘Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem.CreatedDate’ cannot be assigned to – it is read only”

Then you’ll try to bypass the build time error by setting the property using the Fields name value pair by doing something like this:

item.Fields["System.CreatedDate"].Value = date.ToString()

But you get the “InvalidNotOldValue” validation error when calling Validate() and “TF237124: Work Item is not ready to save”

So the only way to do this with TFS 2010 is to bypass the validation rules using the WorkItemServer.Update method. Here’s how you do that:

1. Add the following references to your project:

  • c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.Client.dll
  • c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.WorkItemTracking.Client.dll
  • c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.WorkItemTracking.Proxy.dll

2. Copy the following code into your project or take snippets to meet your needs. The inline comments explain what everything does.

namespace ChangeReadonlyField
{
    using System;
    using System.Text;
    using System.Xml;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.Proxy;

    class Program
    {
        static void Main(string[] args)
        {
            var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("[tfs url"));
            var store = tfs.GetService<WorkItemStore>();
            var server = tfs.GetService<WorkItemServer>();
            var date = new DateTime(2012, 1, 31, 1, 1, 1);

            var id = 1279424;
            var item = store.GetWorkItem(id);

            // When you call update you need to pass the revision number that you want to update.
            // Most of the time ist t makes sense to use the latest revision, which is stored in the Rev property
            var revision = item.Rev.ToString();

            // Create the xml package that is needed when calling the server to update a readonly field.   
            // Make sure you pass in the date using XmlConvert.ToString(date, XmlDateTimeSerializationMode.Local)
            // You won't get the correct date if you just call date.ToString()
            var sb = new StringBuilder();
            sb.Append("<Package>");
            sb.AppendFormat("<UpdateWorkItem ObjectType='WorkItem' BypassRules='1' WorkItemID='&#123;0&#125;' Revision='&#123;1&#125;'>", id, revision);
            sb.Append("<Columns>");
            sb.AppendFormat("<Column Column='System.CreatedDate' Type='DateTime'><Value>&#123;0&#125;</Value></Column>",
                XmlConvert.ToString(date, XmlDateTimeSerializationMode.Local));
            sb.Append("</Columns></UpdateWorkItem></Package>");

            /* This is what the XML looks like
                <Package>
                  <UpdateWorkItem ObjectType='WorkItem' BypassRules='1' WorkItemID='1279424' Revision='11'>
                    <Columns>
                       <Column Column='System.CreatedDate' Type='DateTime'><Value>1/1/2012 1:01:01 AM</Value></Column>
                    </Columns>
                  </UpdateWorkItem>
                </Package>
            */

            // Initialize the params that are needed for the service call
            var mthe = new MetadataTableHaveEntry[0];
            string dbStamp;
            IMetadataRowSets rowsets;
            XmlElement outElement = null;

            // Load the xml string into an XmlDocument so you can pull the document Element that 
            // the service call needs
            var x = new XmlDocument();
            x.LoadXml(sb.ToString());

            // Call WorkItemServer update method to update the readonly fields and bypass the API validation
            server.Update(WorkItemServer.NewRequestId(),
                x.DocumentElement,
                out outElement,
                mthe,
                out dbStamp,
                out rowsets);
        &#125;
    &#125;
&#125;

Jon