TFS: Get And Modify Included And Excluded Pending Changes in Visual Studio

In this post on Stack Overflow, I posted code that allows access to the included/excluded pending changes that the user can modify via the “Pending Changes” tab on the Visual Studio Team Explorer. Here it is again…

Suppose you have created a Visual Studio package and want to find out which pending changes are currently excluded or included. Suppose you might want to modify these sets as well. The TFS client API does not provide any means to do so. Not publicly, that is.

Fortunately, the types that actually run the Team Explorer window and the “Pending Changes” tab are accessible via code. And with reflection, we can use the objects and methods that are used by Visual Studio to include or exclude the pending changes.

What the code I will be presenting here (the class PendingChangesInclusion) does can be shown in an example call from within the Visual Studio Package:

var teamExplorer = (ITeamExplorer)GetService(typeof(ITeamExplorer));
var psi = new PendingChangesInclusion(teamExplorer);

// To see if this works, include all excluded pending changes...
psi.IncludeChanges(psi.ExcludedChanges);

// ... or exclude all included pending changes.
// psi.ExcludeChanges(psi.IncludedChanges);

To achieve this, we need to use the interface Microsoft.TeamFoundation.VersionControl.Controls.PendingChanges.IPendingChangesDataProvider, which is internal to the assembly Microsoft.TeamFoundation.VersionControl.Controls.dll. We will get the object behind the Team Explorer window, and, via Reflection, get an instance of IPendingChangesDataProvide. All further access to that instance can only occur via reflection, since we have no public interface to compile against. But that is not such of a performance problem, since we can pre-compile the access to the properties and methods we need. Of course, using Reflection like this has the drawback of that Microsoft can change the internal code any time and the our code will stop working.

But enough of words! Here’s the code already:

using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.TeamFoundation.Controls;
using Microsoft.TeamFoundation.Controls.WPF.TeamExplorer;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace PrettyFlyForANamespace
{
    public class PendingChangesInclusion
    {
        private readonly Action<IList<PendingChange>> _includeChanges;
        private readonly Action<IList<PendingChange>> _excludeChanges;
        private readonly Func<IList<PendingChange>> _getIncludedChanges;
        private readonly Func<IList<PendingChange>> _getExcludedChanges;
        
        public PendingChangesInclusion(ITeamExplorer teamExplorer)
        {
            var pendingChangesPage = (TeamExplorerPageBase)teamExplorer.NavigateToPage(new Guid(TeamExplorerPageIds.PendingChanges), null);
            
            var model = pendingChangesPage.Model;
            var p = model.GetType().GetProperty("DataProvider", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            
            var dataProvider = p.GetValue(model); // IPendingChangesDataProvider is internal;
            var dataProviderType = dataProvider.GetType();
            
            p = dataProviderType.GetProperty("IncludedChanges", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            var m = p.GetMethod;
            _getIncludedChanges = (Func<IList<PendingChange>>)m.CreateDelegate(typeof(Func<IList<PendingChange>>), dataProvider);
            
            p = dataProviderType.GetProperty("ExcludedChanges", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            m = p.GetMethod;
            _getExcludedChanges = (Func<IList<PendingChange>>)m.CreateDelegate(typeof(Func<IList<PendingChange>>), dataProvider);
            
            m = dataProviderType.GetMethod("IncludeChanges", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            _includeChanges = (Action<IList<PendingChange>>)m.CreateDelegate(typeof(Action<IList<PendingChange>>), dataProvider);
            
            m = dataProviderType.GetMethod("ExcludeChanges", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            _excludeChanges = (Action<IList<PendingChange>>)m.CreateDelegate(typeof(Action<IList<PendingChange>>), dataProvider);
        }

        public void IncludeChanges(IList<PendingChange> changes)
        {
            _includeChanges(changes);
        }

        public void ExcludeChanges(IList<PendingChange> changes)
        {
            _excludeChanges(changes);
        }

        public IList<PendingChange> GetIncludeChanges()
        {
            return _getIncludedChanges();
        }

        public IList<PendingChange> GetExcludedChanges()
        {
            return _getExcludedChanges();
        }
    }
}

For the code to compile, references to following assemblies are needed:

  • C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.Client.dll
  • C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v4.5\Microsoft.TeamFoundation.Controls.dll
  • C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.TeamFoundation.VersionControl.Client.dll

Freelance full-stack .NET and JS developer and architect. Located near Cologne, Germany.

Leave a Reply

Your email address will not be published. Required fields are marked *