How to get all the AD groups for a particular user?

109,706

Solution 1

Just query the "memberOf" property and iterate though the return, example:

            search.PropertiesToLoad.Add("memberOf");
            StringBuilder groupNames = new StringBuilder(); //stuff them in | delimited

                SearchResult result = search.FindOne();
                int propertyCount = result.Properties["memberOf"].Count;
                String dn;
                int equalsIndex, commaIndex;

                for (int propertyCounter = 0; propertyCounter < propertyCount;
                    propertyCounter++)
                {
                    dn = (String)result.Properties["memberOf"][propertyCounter];

                    equalsIndex = dn.IndexOf("=", 1);
                    commaIndex = dn.IndexOf(",", 1);
                    if (-1 == equalsIndex)
                    {
                        return null;
                    }
                    groupNames.Append(dn.Substring((equalsIndex + 1),
                                (commaIndex - equalsIndex) - 1));
                    groupNames.Append("|");
                }

            return groupNames.ToString();

This just stuffs the group names into the groupNames string, pipe delimited, but when you spin through you can do whatever you want with them

Solution 2

You should use System.DirectoryServices.AccountManagement. It's much easier. Here is a nice code project article giving you an overview on all the classes in this DLL.

As you pointed out, your current approach doesn't find out the primary group. Actually, it's much worse than you thought. There are some more cases that it doesn't work, like the domain local group from another domain. You can check here for details. Here is how the code looks like if you switch to use System.DirectoryServices.AccountManagement. The following code can find the immediate groups this user assigned to, which includes the primary group.

UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext (ContextType.Domain, "mydomain.com"), IdentityType.SamAccountName, "username");
foreach (GroupPrincipal group in user.GetGroups())
{
    Console.Out.WriteLine(group);
}

Solution 3

Use tokenGroups:

DirectorySearcher ds = new DirectorySearcher();
ds.Filter = String.Format("(&(objectClass=user)(sAMAccountName={0}))", username);
SearchResult sr = ds.FindOne();

DirectoryEntry user = sr.GetDirectoryEntry();
user.RefreshCache(new string[] { "tokenGroups" });

for (int i = 0; i < user.Properties["tokenGroups"].Count; i++) {
    SecurityIdentifier sid = new SecurityIdentifier((byte[]) user.Properties["tokenGroups"][i], 0);
    NTAccount nt = (NTAccount)sid.Translate(typeof(NTAccount));
    //do something with the SID or name (nt.Value)
}

Note: this only gets security groups

Solution 4

This is how I list all the groups (direct and indirect) for a specific Distinguished Name:

The string 1.2.840.113556.1.4.1941 specifies LDAP_MATCHING_RULE_IN_CHAIN.

This rule is limited to filters that apply to the DN. This is a special "extended" match operator that walks the chain of ancestry in objects all the way to the root until it finds a match.

This method is 25 times faster than the UserPrincipal.GetGroups() method in my testing.

Note: The primary group (typically Domain Users) is not returned by this or GetGroups() method. To get the primary group name too, I've confirmed this method works.

Additionally, I found this list of LDAP filters extremely useful.

private IEnumerable<string> GetGroupsForDistinguishedName(DirectoryEntry domainDirectoryEntry, string distinguishedName)
{
    var groups = new List<string>();
    if (!string.IsNullOrEmpty(distinguishedName))
    {
        var getGroupsFilterForDn = $"(&(objectCategory=group)(member:1.2.840.113556.1.4.1941:={distinguishedName}))";
        using (DirectorySearcher dirSearch = new DirectorySearcher(domainDirectoryEntry))
        {
            dirSearch.Filter = getGroupsFilterForDn;
            dirSearch.PropertiesToLoad.Add("name");

            using (var results = dirSearch.FindAll())
            {
                foreach (SearchResult result in results)
                {
                    if (result.Properties.Contains("name"))
                        groups.Add((string)result.Properties["name"][0]);
                }
            }
        }
    }

    return groups;
}

Solution 5

This code works even faster (two 1.5 faster than my previous version):

    public List<String> GetUserGroups(WindowsIdentity identity)
    {
        List<String> groups = new List<String>();

        String userName = identity.Name;
        int pos = userName.IndexOf(@"\");
        if (pos > 0) userName = userName.Substring(pos + 1);

        PrincipalContext domain = new PrincipalContext(ContextType.Domain, "riomc.com");
        UserPrincipal user = UserPrincipal.FindByIdentity(domain, IdentityType.SamAccountName, userName); // NGeodakov

        DirectoryEntry de = new DirectoryEntry("LDAP://RIOMC.com");
        DirectorySearcher search = new DirectorySearcher(de);
        search.Filter = "(&(objectClass=group)(member=" + user.DistinguishedName + "))";
        search.PropertiesToLoad.Add("cn");
        search.PropertiesToLoad.Add("samaccountname");
        search.PropertiesToLoad.Add("memberOf");

        SearchResultCollection results = search.FindAll();
        foreach (SearchResult sr in results)
        {
            GetUserGroupsRecursive(groups, sr, de);
        }

        return groups;
    }

    public void GetUserGroupsRecursive(List<String> groups, SearchResult sr, DirectoryEntry de)
    {
        if (sr == null) return;

        String group = (String)sr.Properties["cn"][0];
        if (String.IsNullOrEmpty(group))
        {
            group = (String)sr.Properties["samaccountname"][0];
        }
        if (!groups.Contains(group))
        {
            groups.Add(group);
        }

        DirectorySearcher search;
        SearchResult sr1;
        String name;
        int equalsIndex, commaIndex;
        foreach (String dn in sr.Properties["memberof"])
        {
            equalsIndex = dn.IndexOf("=", 1);
            if (equalsIndex > 0)
            {
                commaIndex = dn.IndexOf(",", equalsIndex + 1);
                name = dn.Substring(equalsIndex + 1, commaIndex - equalsIndex - 1);

                search = new DirectorySearcher(de);
                search.Filter = "(&(objectClass=group)(|(cn=" + name + ")(samaccountname=" + name + ")))";
                search.PropertiesToLoad.Add("cn");
                search.PropertiesToLoad.Add("samaccountname");
                search.PropertiesToLoad.Add("memberOf");
                sr1 = search.FindOne();
                GetUserGroupsRecursive(groups, sr1, de);
            }
        }
    }
Share:
109,706

Related videos on Youtube

NLV
Author by

NLV

Web Two O.

Updated on April 01, 2022

Comments

  • NLV
    NLV about 2 years

    I checked this post already. But it doesn't answer my question. I want to get all the active directory groups in which a particular user is a member.

    I've written the following code. But I'm not able to proceed further as I don't know how to give the filter and how to access the properties.

    class Program
    {
        static void Main(string[] args)
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://mydomain.com");
            DirectorySearcher searcher = new DirectorySearcher(de);
            searcher.Filter = "(&(ObjectClass=group))";
            searcher.PropertiesToLoad.Add("distinguishedName");
            searcher.PropertiesToLoad.Add("sAMAccountName");
            searcher.PropertiesToLoad.Add("name");
            searcher.PropertiesToLoad.Add("objectSid");
            SearchResultCollection results = searcher.FindAll();
            int i = 1;
            foreach (SearchResult res in results)
            {
                Console.WriteLine("Result" + Convert.ToString(i++));
                DisplayProperties("distinguishedName", res);
                DisplayProperties("sAMAccouontName", res);
                DisplayProperties("name", res);
                DisplayProperties("objectSid", res);
                Console.WriteLine();
            }
    
            Console.ReadKey();
        }
    
        private static void DisplayProperties(string property, SearchResult res)
        {
            Console.WriteLine("\t" + property);
            ResultPropertyValueCollection col = res.Properties[property];
            foreach (object o in col)
            {
                Console.WriteLine("\t\t" + o.ToString());
            }
        }
    }
    

    Any ideas?

    • NLV
      NLV over 13 years
      Okie. I learnt from here - eggheadcafe.com/software/aspnet/30375857/… that memberOf wont return Primary Groups of a user. But I can get the primaryGroupID which gives the RID(?) of the group using which I need to get the AD group. Any ideas how to do it?
    • Harvey Kwok
      Harvey Kwok over 13 years
      Please check my newly posted answer. Your current approach doesn't work very well. If the user is assigned to an Universal Group in another forest, the Universal Group won't show up in the memberOf attribute
  • NLV
    NLV over 13 years
    Okie. I've recursive found the parent groups. But still two groups (or folders?) 'Domain Users' is not coming in the list. But if I check the 'memberOf' tab it is there. Domain users is a builtin group right? Am I missing something here?
  • NathanOliver
    NathanOliver about 9 years
    Please don't post just code only answers. Please detail what is going on in your answer.
  • Wolf5
    Wolf5 over 7 years
    This code was about 10 times faster than any of the other codes I have tried (using PrincipalContext). Went from about 4 seconds fetching all groups for a user to 400 ms. Thanks.
  • Flexabust Bergson
    Flexabust Bergson almost 7 years
    @Wolf5 I don't understand this, am I supposed to add each NTAccount to a List, for each iteration?
  • Kevin .NET
    Kevin .NET about 6 years
    user.GetGroups() is very slow!
  • Kiquenet
    Kiquenet about 5 years
    how to perform an LDAP search using the LDAP queries provided ?
  • Kiquenet
    Kiquenet about 5 years
    @Kevin.NET very slow? benchmarks ? high performance solution?
  • neumann1990
    neumann1990 about 5 years
    Using a filter of $"(&(objectCategory=group)(member:1.2.840.113556.1.4.1941:={‌​distinguishedName}))‌​" makes the query even more performant. It seems that AD uses the group filter to limit the number of objects it checks (according to Note 19 here: social.technet.microsoft.com/wiki/contents/articles/…). Other than that, this works great :)
  • DDuffy
    DDuffy over 4 years
    I know it has been a while, but do you know how you would separate the Security Groups from the Distribution Lists?
  • DDuffy
    DDuffy over 4 years
    never mind. Worked it out. Adding code for "Future Generations". Pulls the group type and adds it as a second string. StringBuilder groupTypes = new StringBuilder(); secondequalsIndex = dn.IndexOf("=", equalsIndex+1); secondcommaIndex = dn.IndexOf(",", commaIndex+1); groupTypes.Append(dn.Substring((secondequalsIndex + 1),(secondcommaIndex - secondequalsIndex) - 1)); groupTypes.Append("|");
  • Mark
    Mark about 4 years
    Indeed, its an easy solution, but very slow. every item in the foreach takes up some time, so the query is not done in 1 single call, but every time you loop.
  • Chazt3n
    Chazt3n almost 4 years
    @Mark call tolist() on getgroups() if it's calling every time. groups = userPrincipal.GetAuthorizationGroups().ToList(); is decently fast
  • Russ Ebbing
    Russ Ebbing about 3 years
    System.DirectoryServices.AccountManagement will work for querying one but unbelievably slow when querying 500. The reason for this is this class transfers everything over the wire even if you only need to select a few fields.
  • PKCS12
    PKCS12 over 2 years
    Is there anyway of determining the depth of how nested each group is using this method please?