Get members of an Active Directory group recursively, i.e. including subgroups
Asked Answered
R

3

9

Given a group like this in Active Directory:

MainGroup
  GroupA
    User1
    User2
  GroupB
    User3
  User4

I can easily determine if User3 is member of MainGroup or any of its subgroups with code like this:

using System;
using System.DirectoryServices;

static class Program {
    static void Main() {
        DirectoryEntry user = new DirectoryEntry("LDAP://CN=User3,DC=X,DC=y");
        string filter = "(memberOf:1.2.840.113556.1.4.1941:=CN=MainGroup,DC=X,DC=y)";
        DirectorySearcher searcher = new DirectorySearcher(user, filter);
        searcher.SearchScope = SearchScope.Subtree;
        var r = searcher.FindOne();
        bool isMember = (r != null);
    }
}

I would like to know if there is a similar way to get all the users that are member of a group or any of its subgroups, i.e. in the example for MainGroup get User1, User2, User3 and User4.

The obvious way of getting all the users is to recursively query each subgroup, but I was wondering if there is an easier way to do it.

Using the same approach with the memberOf:1.2.840.113556.1.4.1941: filter, but using the domain root instead of the user as a search base is not feasible, as the query takes too long (probably it computes all the group memberships recursively for all users in the domain and checks if they are member of the given group).

Which is the best way to get all members of a group, including its subgroups?

Roping answered 8/9, 2010 at 7:28 Comment(0)
R
25

Just in case this might benefit someone else: here is the solution I ended up with. It is just a recursive search, with some extra checks to avoid checking the same group or user twice, e.g. if groupA is member of groupB and groupB is member of groupA or a user is member of more than one group.

using System;
using System.DirectoryServices;
using System.Collections.Generic;

static class Program {

    static IEnumerable<SearchResult> GetMembers(DirectoryEntry searchRoot, string groupDn, string objectClass) {
        using (DirectorySearcher searcher = new DirectorySearcher(searchRoot)) {
            searcher.Filter = "(&(objectClass=" + objectClass + ")(memberOf=" + groupDn + "))";
            searcher.PropertiesToLoad.Clear();
            searcher.PropertiesToLoad.AddRange(new string[] { 
                "objectGUID",
                "sAMAccountName",
                "distinguishedName"});
            searcher.Sort = new SortOption("sAMAccountName", SortDirection.Ascending);
            searcher.PageSize = 1000;
            searcher.SizeLimit = 0;
            foreach (SearchResult result in searcher.FindAll()) {
                yield return result;
            }
        }
    }

    static IEnumerable<SearchResult> GetUsersRecursively(DirectoryEntry searchRoot, string groupDn) {
        List<string> searchedGroups = new List<string>();
        List<string> searchedUsers = new List<string>();
        return GetUsersRecursively(searchRoot, groupDn, searchedGroups, searchedUsers);
    }

    static IEnumerable<SearchResult> GetUsersRecursively(
        DirectoryEntry searchRoot,
        string groupDn,
        List<string> searchedGroups,
        List<string> searchedUsers) {
        foreach (var subGroup in GetMembers(searchRoot, groupDn, "group")) {
            string subGroupName = ((string)subGroup.Properties["sAMAccountName"][0]).ToUpperInvariant();
            if (searchedGroups.Contains(subGroupName)) {
                continue;
            }
            searchedGroups.Add(subGroupName);
            string subGroupDn = ((string)subGroup.Properties["distinguishedName"][0]);
            foreach (var user in GetUsersRecursively(searchRoot, subGroupDn, searchedGroups, searchedUsers)) {
                yield return user;
            }
        }
        foreach (var user in GetMembers(searchRoot, groupDn, "user")) {
            string userName = ((string)user.Properties["sAMAccountName"][0]).ToUpperInvariant();
            if (searchedUsers.Contains(userName)) {
                continue;
            }
            searchedUsers.Add(userName);
            yield return user;
        }
    }

    static void Main(string[] args) {
        using (DirectoryEntry searchRoot = new DirectoryEntry("LDAP://DC=x,DC=y")) {
            foreach (var user in GetUsersRecursively(searchRoot, "CN=MainGroup,DC=x,DC=y")) {
                Console.WriteLine((string)user.Properties["sAMAccountName"][0]);
            }
        }
    }

}
Roping answered 8/9, 2010 at 13:26 Comment(3)
This post has been here for a little while, but I wanted to add a thanks for posting your answer. I've just run into the same problem, and I'm about to try this out.Fauces
I don't know if you'll see this (or even if this is the most appropriate way to ask a question like this, but I am having some trouble getting this to work, and I think it's probably due to my lack of AD understanding. When I get the distinct name (DN) of my group, it's returning something like this:'CN=<REDACTED>,OU=<REDACTED>,OU=Distribution List,OU=Exchange Services,OU=Core Directory Services,DC=<REDACTED>,DC=com' But when I tried to seach against that (or that without the leading "CN="), I get an "Unspecified Error" at the point where searcher iterated through FindAll()Fauces
@Fauces : I think that the best for you would be to post a new question (with a link to this one if it's a related problem). Right now I cannot help you, as I'm missing a domain to test upon, sorry :)Roping
H
1
    static List<SearchResult> ad_find_all_members(string a_sSearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
    {
        using (DirectoryEntry de = new DirectoryEntry(a_sSearchRoot))
            return ad_find_all_members(de, a_sGroupDN, a_asPropsToLoad);
    }

    static List<SearchResult> ad_find_all_members(DirectoryEntry a_SearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
    {
        string sDN = "distinguishedName";
        string sOC = "objectClass";
        string sOC_GROUP = "group";
        string[] asPropsToLoad = a_asPropsToLoad;
        Array.Sort<string>(asPropsToLoad);
        if (Array.BinarySearch<string>(asPropsToLoad, sDN) < 0)
        {
            Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
            asPropsToLoad[asPropsToLoad.Length-1] = sDN;
        }
        if (Array.BinarySearch<string>(asPropsToLoad, sOC) < 0)
        {
            Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
            asPropsToLoad[asPropsToLoad.Length-1] = sOC;
        }

        List<SearchResult> lsr = new List<SearchResult>();

        using (DirectorySearcher ds = new DirectorySearcher(a_SearchRoot))
        {
            ds.Filter = "(&(|(objectClass=group)(objectClass=user))(memberOf=" + a_sGroupDN + "))";
            //ds.PropertiesToLoad.Clear();
            ds.PropertiesToLoad.AddRange(asPropsToLoad);
            //ds.PageSize = 1000;
            //ds.SizeLimit = 0;
            foreach (SearchResult sr in ds.FindAll())
                lsr.Add(sr);
        }

        for(int i=0;i<lsr.Count;i++)
            if (lsr[i].Properties.Contains(sOC) && lsr[i].Properties[sOC].Contains(sOC_GROUP))
                lsr.AddRange(ad_find_all_members(a_SearchRoot, (string)lsr[i].Properties[sDN][0], asPropsToLoad));

        return lsr;
    }

    static void Main(string[] args)
    {
    foreach (var sr in ad_find_all_members("LDAP://DC=your-domain,DC=com", "CN=your-group-name,OU=your-group-ou,DC=your-domain,DC=com", new string[] { "sAMAccountName" }))
        Console.WriteLine((string)sr.Properties["distinguishedName"][0] + " : " + (string)sr.Properties["sAMAccountName"][0]);
    }
Henpeck answered 18/7, 2011 at 22:47 Comment(0)
H
0

To get the members recursively take this (the trick is GetMembers(true) instead of false which is the default value):

    private List<string> GetGroupMembers(string groupName)
    {
        var members = new List<string>();

        try
        {
            using (var pc = new PrincipalContext(ContextType.Domain, Common.THE_DOMAIN))
            {
                var gp = GroupPrincipal.FindByIdentity(pc, groupName);

                if (gp == null) return members;

                foreach (Principal p in gp.GetMembers(true))
                    members.Add(p.Name);

                members.Sort();
            }
        }
        catch (Exception)
        {
            return new List<string>();
        }

        return members;
    }

I also wanted to know if a user or a computer is in an ActiveDirectory group. And this worked for me also with nested groups (it is part of a WebService but you can use the code also in a standalone application):

    using System.DirectoryServices;
    using System.DirectoryServices.AccountManagement;

    [HttpGet]
    [Route("IsUserInGroup")]
    public HttpResponseMessage IsUserInGroup(string userName, string groupName)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);

        try
        {
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Common.THE_DOMAIN))
            {
                var gp = GroupPrincipal.FindByIdentity(pc, groupName);
                var up = UserPrincipal.FindByIdentity(pc, userName);

                if (gp == null)
                {
                    response.Content = Common.ConvertToJsonContent($"Group '{groupName}' not found in Active Directory");
                    response.StatusCode = HttpStatusCode.NotFound;
                    return response;
                }

                if (up == null)
                {
                    response.Content = Common.ConvertToJsonContent($"User '{userName}' not found in Active Directory");
                    response.StatusCode = HttpStatusCode.NotFound;
                    return response;
                }

                DirectoryEntry user = new DirectoryEntry($"LDAP://{up.DistinguishedName}");
                DirectorySearcher mySearcher = new DirectorySearcher(user)
                {
                    SearchScope = System.DirectoryServices.SearchScope.Subtree,
                    Filter = $"(memberOf:1.2.840.113556.1.4.1941:={gp.DistinguishedName})"
                };

                SearchResult result = mySearcher.FindOne();

                response.StatusCode = HttpStatusCode.OK;
                response.Content = Common.ConvertToJsonContent(result != null);
            }
        }
        catch (Exception ex)
        {
            response.StatusCode = HttpStatusCode.BadRequest;
            response.Content = Common.ConvertToJsonContent($"{MethodBase.GetCurrentMethod().Name}: {ex.Message}");
        }

        return response;
    }

    [HttpGet]
    [Route("IsComputerInGroup")]
    public HttpResponseMessage IsComputerInGroup(string computerName, string groupName)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);

        try
        {
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Common.THE_DOMAIN))
            {
                var gp = GroupPrincipal.FindByIdentity(pc, groupName);
                var cp = ComputerPrincipal.FindByIdentity(pc, computerName);

                if (gp == null)
                {
                    response.Content = Common.ConvertToJsonContent($"Group '{groupName}' not found in Active Directory");
                    response.StatusCode = HttpStatusCode.NotFound;
                    return response;
                }

                if (cp == null)
                {
                    response.Content = Common.ConvertToJsonContent($"Computer '{computerName}' not found in Active Directory");
                    response.StatusCode = HttpStatusCode.NotFound;
                    return response;
                }

                DirectoryEntry computer = new DirectoryEntry($"LDAP://{cp.DistinguishedName}");
                DirectorySearcher mySearcher = new DirectorySearcher(computer)
                {
                    SearchScope = System.DirectoryServices.SearchScope.Subtree,
                    Filter = $"(memberOf:1.2.840.113556.1.4.1941:={gp.DistinguishedName})"
                };

                SearchResult result = mySearcher.FindOne();

                response.StatusCode = HttpStatusCode.OK;
                response.Content = Common.ConvertToJsonContent(result != null);
            }
        }
        catch (Exception ex)
        {
            response.StatusCode = HttpStatusCode.BadRequest;
            response.Content = Common.ConvertToJsonContent($"{MethodBase.GetCurrentMethod().Name}: {ex.Message}");
        }

        return response;
    }
Hassi answered 18/11, 2022 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.