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.
It’s working and useful 🙂
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
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
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.
Thank you for posting this, helped me much 🙂
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
}
Thanks Fred. I didn’t realize that. Btw, will it detect circular references that Jim was asking about in above comments?
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’}
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.