Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fast way to get a list of group members in Active Directory with C#

In a web app, we're looking to display a list of sam accounts for users that are a member of a certain group. Groups could have 500 or more members in many cases and we need the page to be responsive.

With a group of about 500 members it takes 7-8 seconds to get a list of sam accounts for all members of the group. Are there faster ways? I know the Active Directory Management Console does it in under a second.

I've tried a few methods:

1)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
List<string> lst = grp.Members.Select(g => g.SamAccountName).ToList();

2)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
PrincipalSearchResult<Principal> lstMembers = grp.GetMembers(true);
List<string> lst = new List<string>();
foreach (Principal member in lstMembers )
{
    if (member.StructuralObjectClass.Equals("user"))
    {
        lst.Add(member .SamAccountName);
    }
}

3)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
System.DirectoryServices.DirectoryEntry de = (System.DirectoryServices.DirectoryEntry)grp.GetUnderlyingObject();
List<string> lst = new List<string>();
foreach (string sDN in de.Properties["member"])
{
    System.DirectoryServices.DirectoryEntry deMember = new System.DirectoryServices.DirectoryEntry("LDAP://" + sDN);
    lst.Add(deMember.Properties["samAccountName"].Value.ToString());
}
like image 906
Jeremy Avatar asked Jun 24 '11 22:06

Jeremy


People also ask

How do I get a list of members of ad group?

You can use the Get-ADGroupMember cmdlet to get a list of all members of the AD group. Members can be users, groups, or computers. In PowerShell to list ad group members of a specific group, use the Identity parameter.

How can I see the members of AD group in CMD?

You can check active directory group membership using the command line net user or dsget or using the Get-AdGroupMember PowerShell cmdlet to check ad group membership.

How do I see members of a domain group?

Right-click on the domain root and select Find; Enter a username and click Find Now; Open the user properties and go to the Member of tab; This tab lists the groups the selected user is a member of.


1 Answers

If you want speed, don't use the System.DirectoryServices.AccountManagement namespace at all (GroupPrincipal, UserPrincipal, etc.). It makes coding easier, but it's slloooowwww.

Use only DirectorySearcher and DirectoryEntry. (The AccountManagement namespace is just a wrapper for this anyway)

I had this discussion with someone else not too long ago. You can read the full chat here, but in one case where a group had 4873 members, AccountManagement's GetMember() method took 200 seconds, where using DirectoryEntry took only 16 seconds.

However, there are a few caveats:

  1. Do not look at the memberOf attribute (as JPBlanc's answer suggests). It will not find members of Domain Local groups. The memberOf attribute only shows Universal groups, and Global groups only on the same domain. Domain Local groups don't show up there.
  2. Looking at the member attribute of a group will only feed you the members 1500 at a time. You have to retrieve the members in batches of 1500.
  3. It's possible for an account to have the primaryGroupId set to any group, and be considered part of that group (but not show up in the member attribute of that group). This is usually only the case with the Domain Users group.
  4. If a Domain Local group has users from an external domain, they show up as Foreign Security Principal objects, which hold the SID for the actual account on the external domain. Some extra work needs to be done to find the account on the external domain.

The AccountManagement namespace's GetMember() method takes care of all these things, just not as efficiently as it could.

When helping that other user, I did put together a method that will cover the first three issues above, but not #4. It's the last code block in this answer: https://stackoverflow.com/a/49241443/1202807

Update:

(I've documented all of this on my site here: Find all the members of a group)

You mentioned that the most time-consuming part is looping through the members. That's because you're binding to each member, which is understandable. You can lessen that by calling .RefreshCache() on the DirectoryEntry object to load only the properties you need. Otherwise, when you first use Properties, it'll get every attribute that has a value, which adds time for no reason.

Below is an example I used. I tested with a group that has 803 members (in nested groups) and found that having the .RefreshCache() lines consistently shaved off about 10 seconds, if not more (~60s without, ~45-50s with).

This method will not account for points 3 & 4 that I mentioned above. For example, it will silently ignore Foreign Security Principals. But if you only have one domain with no trusts, you have no need to care.

private static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false) {
    var members = new List<string>();

    group.RefreshCache(new[] { "member" });

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            var memberDe = new DirectoryEntry($"LDAP://{member}");
            memberDe.RefreshCache(new[] { "objectClass", "sAMAccountName" });
            if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
                members.AddRange(GetGroupMemberList(memberDe, true));
            } else {
                var username = memberDe.Properties["sAMAccountName"]?.Value?.ToString();
                if (!string.IsNullOrEmpty(username)) { //It will be null if this is a Foreign Security Principal
                    members.Add(username);
                }
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*"});
        } catch (COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }

    return members;
}
like image 199
Gabriel Luci Avatar answered Sep 21 '22 21:09

Gabriel Luci