Customers find it necessary at times to extract information from TFS about groups and their membership. This can arise in the context of auditing, custom report generation, or building custom extensions to TFS. The following sample shows how this can be accomplished quite easily using the Identity Management Service API's. The sample can extract all groups and members in the Team Foundation server, in a Team Project Collection, or just scope it to a Team Project.

To compile and run, add references to Microsoft.TeamFoundation.Client and Microsoft.TeamFoundation.Common assemblies.

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;

namespace TFSGroupMembersFast
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter TFS server or project collection Url");
            Console.Write("e.g. http://localhost:8080/tfs/defaultcollection: ");
            string address = Console.ReadLine();
            Console.WriteLine();

            Console.WriteLine("Enter Team Project Uri for project scope, else empty string");
            Console.Write("e.g. vstfs:///Classification/TeamProject/****: ");
            string projectUri = Console.ReadLine();
            Console.WriteLine();

            DateTime startTime = DateTime.Now;

            TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(TfsTeamProjectCollection.GetFullyQualifiedUriForName(address));
            _ims = tfs.GetService<IIdentityManagementService>();

            if (string.IsNullOrEmpty(projectUri))
            {
                TeamFoundationIdentity group = _ims.ReadIdentity(GroupWellKnownDescriptors.EveryoneGroup, MembershipQuery.Direct, ReadIdentityOptions.None);
                m_identities[group.Descriptor] = group;
                m_groups.Add(group);

                // Get expanded membership of the Valid Users group, which is all identities in this host            
                group = _ims.ReadIdentity(GroupWellKnownDescriptors.EveryoneGroup, MembershipQuery.Expanded, ReadIdentityOptions.None);
                FetchIdentities(group.Members);
            }
            else
            {
                // Get all groups in this project
                TeamFoundationIdentity[] projectGroups = _ims.ListApplicationGroups(projectUri, ReadIdentityOptions.None);

                Dictionary<IdentityDescriptor, object> descSet = new Dictionary<IdentityDescriptor, object>(IdentityDescriptorComparer.Instance);

                foreach (TeamFoundationIdentity projectGroup in projectGroups)
                {
                    descSet[projectGroup.Descriptor] = projectGroup.Descriptor;
                }

                // Expanded membership of project groups
                projectGroups = _ims.ReadIdentities(descSet.Keys.ToArray(), MembershipQuery.Expanded, ReadIdentityOptions.None);

                // Collect all descriptors
                foreach (TeamFoundationIdentity projectGroup in projectGroups)
                {
                    foreach (IdentityDescriptor mem in projectGroup.Members)
                    {
                        descSet[mem] = mem;
                    }
                }

                FetchIdentities(descSet.Keys.ToArray());
            }

            // Now output groups and their members.
            foreach (TeamFoundationIdentity identity in m_groups)
            {
                Write(identity);
            }

            Console.WriteLine(String.Format("======= Finished reading {0} identities in {1} minutes",
                m_identities.Count, (DateTime.Now - startTime).TotalMinutes));

            Console.ReadLine();
        }

        static void FetchIdentities(IdentityDescriptor[] descriptors)
        {
            TeamFoundationIdentity[] identities;

            // If total membership exceeds batch size limit for Read, break it up
            int batchSizeLimit = 100000;

            if (descriptors.Length > batchSizeLimit)
            {
                int batchNum = 0;
                int remainder = descriptors.Length;
                IdentityDescriptor[] batchDescriptors = new IdentityDescriptor[batchSizeLimit];

                while (remainder > 0)
                {
                    int startAt = batchNum * batchSizeLimit;
                    int length = batchSizeLimit;
                    if (length > remainder)
                    {
                        length = remainder;
                        batchDescriptors = new IdentityDescriptor[length];
                    }

                    Array.Copy(descriptors, startAt, batchDescriptors, 0, length);
                    identities = _ims.ReadIdentities(batchDescriptors, MembershipQuery.Direct, ReadIdentityOptions.None);
                    SortIdentities(identities);
                    remainder -= length;
                }
            }
            else
            {
                identities = _ims.ReadIdentities(descriptors, MembershipQuery.Direct, ReadIdentityOptions.None);
                SortIdentities(identities);
            }
        }

        static void SortIdentities(TeamFoundationIdentity[] identities)
        {
            foreach (TeamFoundationIdentity identity in identities)
            {
                m_identities[identity.Descriptor] = identity;

                if (identity.IsContainer)
                {
                    m_groups.Add(identity);
                }
            }
        }

        static void Write(TeamFoundationIdentity group)
        {
            // Output this group's membership
            Console.WriteLine("Members of group: {0}", group.DisplayName);
            Console.WriteLine("=================");

            foreach (IdentityDescriptor memDesc in group.Members)
            {
                Console.WriteLine(m_identities[memDesc].DisplayName);
            }

            Console.WriteLine();
        }

        static Dictionary<IdentityDescriptor, TeamFoundationIdentity> m_identities =
            new Dictionary<IdentityDescriptor, TeamFoundationIdentity>(IdentityDescriptorComparer.Instance);
        static List<TeamFoundationIdentity> m_groups = new List<TeamFoundationIdentity>();

        static IIdentityManagementService _ims;
    }
}