Pierluc SS
Pierluc SS

Reputation: 3176

Get users that are 'memberof' a group

I got a working solution, however I'm pretty sure there is a less resource-intensive method because the current solution involves doing a query to get the groups member and then a query to get each users information.

Here is the code I have :

DirectoryEntry root = new DirectoryEntry( "LDAP://server:port" );
DirectorySearcher searcher = new DirectorySearcher( root );
searcher.Filter = "(&(ObjectClass=Group)(CN=foo-group))";

var members = (IEnumerable)searcher.FindOne()
              .GetDirectoryEntry()
              .Invoke( "members" );

Dictionary<string , string> results = new Dictionary<string , string>();

foreach( object member in members ) {
   DirectoryEntry de = new DirectoryEntry( member );
   results.Add( de.Properties[ "SAMAccountname" ][ 0 ].ToString(), de.Properties[ "cn" ][ 0 ].ToString() );
}

Ideally I'd like to be able to do a single query to get every user that are member of a group, filters the properties to load and then display them. So something like this

DirectoryEntry root = new DirectoryEntry( "LDAP://server:port" );
DirectorySearcher searcher = new DirectorySearcher( root );
searcher.PropertiesToLoad.Add( "cn" );
searcher.PropertiesToLoad.Add( "SAMAccountname" );
searcher.Filter = "(&(ObjectClass=user)(memberof=foo-group))";

foreach( var user in searcher.FindAll() ) {
    //do whatever...
}

Unfortunately, that doesn't work for some reason.

Upvotes: 11

Views: 41262

Answers (5)

CrossBound
CrossBound

Reputation: 146

I know that this is an old question, but for the sake of others that find this post, here's a solution that seems to work for me and performs well:

I found JPBlanc's answer to this question How do I filter an LDAP query for groups containing a specific user?. He linked to MS documentation Search Filter Syntax. In the documentation you can see that you can also use the LDAP_MATCHING_RULE_IN_CHAIN OID to do a search on the memberof attribute.

The LDAP_MATCHING_RULE_IN_CHAIN is a matching rule OID that is designed to provide a method to look up the ancestry of an object. Many applications using AD and AD LDS usually work with hierarchical data, which is ordered by parent-child relationships. Previously, applications performed transitive group expansion to figure out group membership, which used too much network bandwidth; applications needed to make multiple roundtrips to figure out if an object fell "in the chain" if a link is traversed through to the end.

An example of such a query is one designed to check if a user "user1" is a member of group "group1". You would set the base to the user DN (cn=user1, cn=users, dc=x) and the scope to base, and use the following query.

(memberof:1.2.840.113556.1.4.1941:=cn=Group1,OU=groupsOU,DC=x)

Here's code that uses this OID to search for users of a group including any attributes you want to retrieve all in one go. This takes only around 120 milliseconds for me. We have a fairly small AD deployment, so your mileage will vary.

void Main()
{
    var searchGroupDN = "<the DN of your group you are trying to find members for>";
    var rootContainerDN = "<DN of a container that will have all your group members under it>";
    var resultList = new List<UserDetail>();
    var beforeSearch = Stopwatch.GetTimestamp();
    
    using (var searchRoot = new DirectoryEntry($"LDAP://{rootContainerDN}"))
    using (var searcher = new DirectorySearcher(searchRoot, $"(&(objectClass=user)(memberof:1.2.840.113556.1.4.1941:={searchGroupDN}))"))
    {
        searcher.SearchScope = SearchScope.Subtree;
        searcher.PropertiesToLoad.Add("objectguid");
        searcher.PropertiesToLoad.Add("sAMAccountName");
        searcher.PropertiesToLoad.Add("displayname");

        foreach (SearchResult searchResult in searcher.FindAll())
        {
            var guid = new Guid((byte[])searchResult.Properties["objectGuid"][0]);
            var userName = (string)searchResult.Properties["sAMAccountName"][0];
            var displayName = (string)searchResult.Properties["displayname"][0];
            resultList.Add(new UserDetail(guid, userName, displayName));
        }
    }
    
    var elapsed = Stopwatch.GetElapsedTime(beforeSearch);
}

public record UserDetail(Guid Id, string UserName, string DisplayName);

Upvotes: 1

Jetti
Jetti

Reputation: 2458

using System.DirectoryServices;

DirectoryEntry objEntry = new DirectoryEntry(Ldapserver, userid, password);
DirectorySearcher personSearcher = new DirectorySearcher(objEntry);
personSearcher.Filter = string.Format("(SAMAccountName={0})", username);
SearchResult result = personSearcher.FindOne();

if(result != null)
{
    DirectoryEntry personEntry = result.GetDirectoryEntry();
    PropertyValueCollection groups = personEntry.Properties["memberOf"];
    foreach(string g in groups)
    {
        Console.WriteLine(g); // will write group name
    }
}

I originally used a method very similar to what you have posted and it took about 12 minutes to run through my entire company's AD and get the results. After switching to this method, it takes about 2 minutes. You will need to use the ldapserver address where I wrote ldapserver and the userid and password as well and username is the SAMAccountName for the person you're looking up.

Upvotes: 4

McX
McX

Reputation: 1376

It's shorter using GroupPrincipal method FindByIdentity which gives also multiple ways to identify the group with IdentityType :

        using (var context = new PrincipalContext(ContextType.Domain, "YOUR_DOMAIN_NAME")
        {
            var userPrincipals = GroupPrincipal
                .FindByIdentity(context, IdentityType.SamAccountName, "GROUP_ACCOUNT")
                .GetMembers(true) // recursive
                .OfType<UserPrincipal>();
            ...
        }

Upvotes: 0

M.Babcock
M.Babcock

Reputation: 18965

If you can use System.DirectoryServices.AccountManagement:

var context = new PrincipalContext(ContextType.Domain, "YOUR_DOMAIN_NAME");
using (var searcher = new PrincipalSearcher())
{
    var groupName = "YourGroup";
    var sp = new GroupPrincipal(context, groupName);
    searcher.QueryFilter = sp;
    var group = searcher.FindOne() as GroupPrincipal;

    if (group == null)
        Console.WriteLine("Invalid Group Name: {0}", groupName);

    foreach (var f in group.GetMembers())
    {
        var principal = f as UserPrincipal;

        if (principal == null || string.IsNullOrEmpty(principal.Name))
            continue;

        Console.WriteLine("{0}", principal.Name);
    }
}

I have some VB code that'll do it the old way also, but this is definitely simpler with AccountManagement.


Here's the VB code I was referring to (again it isn't pretty but it's functional):

Public Function GetUsersByGroup(de As DirectoryEntry, groupName As String) As IEnumerable(Of DirectoryEntry)
    Dim userList As New List(Of DirectoryEntry)
    Dim group As DirectoryEntry = GetGroup(de, groupName)

    If group Is Nothing Then Return Nothing

    For Each user In GetUsers(de)
        If IsUserInGroup(user, group) Then
            userList.Add(user)
        End If
    Next

    Return userList
End Function

Public Function GetGroup(de As DirectoryEntry, groupName As String) As DirectoryEntry
    Dim deSearch As New DirectorySearcher(de)

    deSearch.Filter = "(&(objectClass=group)(SAMAccountName=" & groupName & "))"

    Dim result As SearchResult = deSearch.FindOne()

    If result Is Nothing Then
        Return Nothing
    End If

    Return result.GetDirectoryEntry()
End Function

Public Function GetUsers(de As DirectoryEntry) As IEnumerable(Of DirectoryEntry)
    Dim deSearch As New DirectorySearcher(de)
    Dim userList As New List(Of DirectoryEntry)

    deSearch.Filter = "(&(objectClass=person))"

    For Each user In deSearch.FindAll()
        userList.Add(user.GetDirectoryEntry())
    Next

    Return userList
End Function

Public Function IsUserInGroup(user As DirectoryEntry, group As DirectoryEntry) As Boolean
    Dim memberValues = user.Properties("memberOf")

    If memberValues Is Nothing OrElse memberValues.Count = 0 Then Return False

    For Each g In memberValues.Value
        If g = group.Properties("distinguishedName").Value.ToString() Then
            Return True
        End If
    Next

    Return False
End Function

And usage:

Dim entries = New DirectoryEntry("LDAP://...")
Dim userList As IEnumerable(Of DirectoryEntry) = GetUsersByGroup(entries, "GroupName")

Upvotes: 13

Christoph Fink
Christoph Fink

Reputation: 23093

If you check HERE you can do the following:

DirectoryEntry group = new DirectoryEntry("LDAP://CN=foo-group,DC=Cmp,DC=COM");
foreach(object dn in group.Properties["member"] )
    //do whatever

Upvotes: 2

Related Questions