≡ Menu

PowerShell: How to get nested Active Directory group members

This post helps you to understand how to query nested group members using powershell. The MS given ActiveDirectory powershell module doesn’t provide a direct way to get all the members part of a security group. This information is useful because you can know who all will get permissions granted to a particular security group if the security group has sub groups inside it. If there is just one or two levels of sub groups, then maybe we can spend time and write code for querying those groups as well by parsing their names. But how we can handle the situation where we don’t know how many sub groups the group we are querying has and how many levels are there?

To address this requirement I have written a small powershell function that helps you to get all direct and indirect members of a security group in active directory.

function Get-ADNestedGroupMembers {
[cmdletbinding()]
param (
[String] $GroupName
)            

import-module activedirectory
$Members = Get-ADGroupMember -Identity $GroupName
$members | % {
    if($_.ObjectClass -eq "group") {
        Get-ADNestedGroupMembers -GroupName $_.distinguishedName
    } else {
        return $_.distinguishedname
    }
}            

}

In this code I am using Get-ADGroupMember cmdlet which is part of activedirectory module. This code uses recursive function call to query group members when a sub group is found.

Usage:

Hope this helps… please feel free to post in comments section if you have any questions. This script can be enhanced to display objects of a particular type — for example, only computers, only users etc. I am doing it here …but let me know if you have the requirement, I will add the code for that as well.

You can export the output to a file using below command.

Get-ADNestedGroupMembers -GroupName "Test1" | out-file -Filepath c:\temp\test1.txt

 

Hope this helps…

Comments on this entry are closed.

  • Uma January 3, 2012, 1:24 pm

    It’s working and useful 🙂

  • Lepide January 20, 2012, 6:37 pm

    1: using System;
    2: using System.DirectoryServices;
    3: using System.Collections.Generic;
    4: using System.Text;
    5: using System.Collections.ObjectModel;
    6:
    7: namespace MyNamespace
    8: {
    9: public class GetAllUsers
    10: {
    11: ///
    12: /// This method takes security group alias and fetches list of user alias that are member of this SG
    13: ///
    14: /// Security group alias
    15: /// List of String
    16: public List GetADSecurityGroupUsers(String sgAlias)
    17: {
    18: string path = “GC://DC=corp,DC=microsoft,DC=com”;
    19: string filter;
    20: string filterFormat = “(cn={0})”;
    21: filter = String.Format(filterFormat, sgAlias);
    22:
    23: //Get all the AD directory entries of this security group.
    24: PropertyCollection properties = CISFLDAP.GetADPropertiesForSingleEntry(path, filter);
    25:
    26: List groupMembers= new List();
    27:
    28: if (properties != null)
    29: {
    30: //Used to limit AD search of members to only security groups and users.
    31: //The filter is built here but only really needed in the recursive method called below.
    32: //Was placed here for performance reasons. Has no affect on AD call in this method.
    33: Collection sAMAccountTypes = new Collection();
    34: sAMAccountTypes.Add((int)AuthZGlobals.sAMAccountType.SecurityGroup);
    35: sAMAccountTypes.Add((int)AuthZGlobals.sAMAccountType.User);
    36:
    37: //Builds the filter based on the sAMAccountTypes in the collection.
    38: string sAMAccountFilter = CISFLDAP.MakesAMAccountTypeQueryString(sAMAccountTypes);
    39:
    40: //Look up all the members of this directory entry and look for person data
    41: //to add to the collection.
    42: groupMembers = GetUsersInGroup(properties, groupMembers, sAMAccountFilter); //GetUsersInGroup do not return anything.
    43: }
    44:
    45: return groupMembers;
    46: }
    47:
    48: #region GetUsersInGroup
    49: ///
    50: /// Recurses through the group’s member records and collects all the users.
    51: ///
    52: /// The collection of the directory entry’s properties.
    53: /// List of users found.
    54: /// sAMAccountTypes to filter the AD search on.
    55: private List GetUsersInGroup(PropertyCollection properties, List groupMembers, string filter)
    56: {
    57: string pathFormat = “GC://{0}”;
    58: string memberIdx = “member”;
    59: string sAMAccountNameIdx = “sAMAccountName”;
    60: string sAMAccountTypeIdx = “sAMAccountType”;
    61: string personnelNumberIdx = “extensionAttribute4”;
    62:
    63: if (properties[memberIdx] != null)
    64: {
    65: foreach (object property in properties[memberIdx])
    66: {
    67: string distinguishedName = property.ToString();
    68:
    69: string path = String.Format(pathFormat, distinguishedName);
    70:
    71: //Get the directory entry for this member record. Filters for only the sAMAccountTypes
    72: //security group and user.
    73: PropertyCollection childProperties = CISFLDAP.GetADPropertiesForSingleEntry(path, filter);
    74:
    75: if (childProperties != null)
    76: {
    77: //If the member’s sAMAccountType is User.
    78: if ((int)childProperties[sAMAccountTypeIdx].Value == (int)AuthZGlobals.sAMAccountType.User)
    79: {
    80: //If member is a user then add, else member is a Service Account with no
    81: //personnel number.
    82: if (childProperties[personnelNumberIdx].Value != null)
    83: groupMembers.Add(searchResults.Properties[sAMAccountNameIdx].Value.ToString());
    84: else
    85: {
    86: //You’ll need to decide if you want to capture the Service Acount info.
    87: }
    88: }
    89: else
    90: //RECURSE – Look up all the members of the security group just found.
    91: GetUsersInGroup(childProperties, groupMembers, filter); // entry is not declared anywhere
    92: }
    93: }
    94: }
    95: }
    96:
    97: #endregion

  • Jim DeVries February 8, 2012, 11:55 pm

    Thanks for writing this up. I used your code and ran into a situation where there were circular references within nested groups. I added another parameter to control the depth of the recursion. It would be better yet if the code knew what groups had been enumerated already and skipped them specifically. (I also threw in a sort so the results align with what you see in ADUC.)

    function Get-ADNestedGroupMembers {
    [cmdletbinding()]
    param (
    [String] $GroupName,
    [Int] $Depth
    )
    $Members = Get-ADGroupMember -Identity $GroupName | sort
    $members | % {
    if(($_.ObjectClass -eq “group”) -and ($depth -le 4)) {
    Get-ADNestedGroupMembers -GroupName $_.distinguishedName -Depth ($Depth + 1)
    } else {
    return $_.distinguishedname
    }
    }
    }

    import-module activedirectory
    Get-ADNestedGroupMembers -GroupName “RDP – AppServer1” -Depth 1

    • Sitaram Pamarthi February 10, 2012, 3:47 pm

      That is a very good point, Jim. Having -Depth parameter helps to some extent. Like you said, script should cache the parsed group names and skip if it comes across it again. I will extend my code.

  • Rocky February 23, 2012, 4:21 am

    Thank you for posting this, helped me much 🙂

  • Fred February 24, 2012, 6:15 am

    The AD PowerShell command Get-ADGroupMember does in fact have a recusrive switch that does exactly this. To pretty up the output all you have to do is something like this:

    $AllMembers = Get-ADGroupMember -Identity “Domain Admins” -Recursive
    ForEach ($Member in $AllMembers) {
    Write-Output $Member.distinguishedname
    }

    • Sitaram Pamarthi February 24, 2012, 10:55 am

      Thanks Fred. I didn’t realize that. Btw, will it detect circular references that Jim was asking about in above comments?

      • Matt May 11, 2016, 2:32 am

        I realize that this is 4 years old, but for future web searchers… The -Recursive switch does not detect circular references. It will eventually just time out. Aside from circular reference issues, you could easily filter the results to only include users and computers (which does exclude more than groups, such as ManagedServiceAccounts) by piping the output to Where{$_.objectclass -eq ‘User’ -or $_.objectclass -eq ‘Computer’} or simply exclude groups but piping to Where{$_.objectclass -ne ‘Group’}

  • Sitaram Pamarthi May 31, 2012, 8:15 pm

    Fred, I have answer for your question, why not “-Recursive” switch. It seem to be listing the names of the child groups as well along with the members which defeats the purpose as we are just interested in user/computer members. Hope this answers your question.