The problem

There is a really convenient method that can be used to retrieve a flat list of all the groups (event nested) given a known UserPrincipal, e.g. the current user, the really amazing thing is that the method saves you from doing a group recursion to retrieve the groups relationships in order to detect that a user belongs to a group A that belongs to a group B.

The above method utilizes, as far as I know, the security tokens attached to a given user principal and then resolves the group names given the group SID.

The problem is that that method can fail with a really strange exception “NoMatchingPrincipalException” (the group cannot be found) that is typically related to 2 different scenarios:

  1. One, or more, of the group has SID History enabled;
  2. In a complex domain topology one of the DC is not available at the moment of the query;

To make a long story short: we simply cannot rely on that method, period.

The solution

IEnumerable<String> GetGroups( String samAccountName )
{
var userNestedMembership = new List<string>();

var domainConnection = new DirectoryEntry();
domainConnection.AuthenticationType = System.DirectoryServices.AuthenticationTypes.Secure;

var samSearcher = new DirectorySearcher();

samSearcher.SearchRoot = domainConnection;
samSearcher.Filter = "(samAccountName=" + samAccountName + ")";
samSearcher.PropertiesToLoad.Add( "displayName" );

var samResult = samSearcher.FindOne();

if ( samResult != null )
{
var theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache( new string[] { "tokenGroups" } );

foreach ( byte[] resultBytes in theUser.Properties[ "tokenGroups" ] )
{
var SID = new SecurityIdentifier( resultBytes, 0 );
var sidSearcher = new DirectorySearcher();

sidSearcher.SearchRoot = domainConnection;
sidSearcher.Filter = "(objectSid=" + SID.Value + ")";
sidSearcher.PropertiesToLoad.Add( "name" );

var sidResult = sidSearcher.FindOne();
if ( sidResult != null )
{
userNestedMembership.Add( ( string )sidResult.Properties[ "name" ][ 0 ] );
}
}
}

return userNestedMembership;
}

A bunch of old good directory services related code :-) where the really really really, have I said really?, important piece is the following:

theUser.RefreshCache( new string[] { "tokenGroups" } );

That asks to the underlying system to refresh the cache of the groups security tokens attached to the given user.

.m