Summary: Learn about the nuances involved in reporting group memberships with Active Directory PowerShell.
Microsoft Scripting Guy, Ed Wilson, is here. Today we continue our series about Active Directory PowerShell by Ashley McGlone. If you missed it, you may enjoy reading Get Started with Active Directory PowerShell first. Now, here's Ashley...
Security priority
In our world today, security is getting more focus with every headline about data breaches. One key strategy to keep your environment secure is regular audits of group memberships. Hopefully, you or your company’s information security team are regularly checking group memberships. For example, I’ve met many customers who get an email every time a sensitive group membership changes (for example, JoeUser was added to Enterprise Admins).
Who’s on first?
In, there are multiple techniques for reporting group memberships. Today, I will share with you some of the nuances involved. Here are the questions we really want to answer on an operational level with scripting:
- Who are all of the Domain Admins? (Traversing down the membership tree)
- Is JoeUser a member of Domain Admins? (Traversing up the membership tree)
- How did JoeUser get added? (By whom? When? Where?)
I’ve referenced the Domain Admins group here—but you could substitute any group name of interest.
Down the rabbit hole
Group nesting makes these questions a challenge for new scripters. I’ve seen people write some rather interesting code to do their own recursion routines to mine group memberships. Although those are fun to write, there are some built-in cmdlets to help. Let’s look at what you get for free before launching into a scripting project.
Who are all the Domain Admins?
The cmdlet Get-ADGroupMember gives you a list of group members. (I’ve heard some complaints that it doesn’t do too well with cross-domain memberships, however.) Immediate members are helpful, but we want to know who is a member of all those nested groups. Thankfully, the cmdlet has a ‑Recursive parameter. Here is a quote from the Help:
If the Recursive parameter is specified, the cmdlet gets all members in the hierarchy of the group that do not contain child objects. For example, if the group SaraDavisReports contains the user KarenToh and the group JohnSmithReports, and JohnSmithReports contains the user JoshPollock, then the cmdlet returns KarenToh and JoshPollock.
Note When you use this parameter you will not see the nested group names, only the members of all the nested groups. The nice thing here is that you can get all true group members even if a user is nested in groups many layers deep.
The -Recursive parameter is resource intensive and should be used with care. Be a good steward of resources by running your query once and storing it in a variable. Then work with the variable instead of issuing the same query multiple times.
$members = Get-ADGroupMember “Domain Admins” -Recursive
Going the other way
What if I want to start with the user and find all of their group memberships recursively (that is, going up the membership tree)? The Get-ADPrincipalGroupMembership cmdlet is your first choice. Here is a quote from the Help:
The Get-ADPrincipalGroupMembership cmdlet gets the Active Directory groups that have a specified user, computer, group, or service account as a member. This cmdlet requires a global catalog to perform the group search. If the forest that contains the user, computer or group does not have a global catalog, the cmdlet returns a non-terminating error. If you want to search for local groups in another domain, use the ResourceContextServer parameter to specify the alternate server in the other domain.
It has bells and whistles for a number of search scenarios. By best practice, all of your domain controllers are likely global catalogs, so that requirement should not be a concern.
$members = Get-ADPrincipalGroupMembership JoeUser
However, this cmdlet does not have a parameter to do a recursive nested group search. It only shows the immediate group membership of a user. If you want to map out all of the nested group memberships, see Token Bloat Troubleshooting by Analyzing Group Nesting in AD on the Active Directory PowerShell blog.
Is JoeUser a nested member of Domain Admins?
How can I traverse up the group nesting to check if JoeUser is in Domain Admins? You do not need to reinvent the wheel. You do not need to write a bunch of code to trace all the nested groups. This happens to be a little-known feature built into the LDAP protocol, and it is supported by Windows Server 2008 and later. I discovered this trick when reading through the Get-Help about_ActiveDirectory_Filter Help topic under Example 11. To quote the Help:
The LDAP_MATCHING_RULE_IN_CHAIN is a matching rule OID that is designed to provide a method to look up the ancestry of an object.
We can use the -RecursiveMatch operator in a filter string to employ this LDAP feature. It searches all of the parent-child relationships of any Active Directory object.
Pro Tip If you cannot find the about_ActiveDirectory* Help topics, first make sure you have the RSAT installed for Active Directory. Then run Update-Help -Module ActiveDirectory in an elevated Windows PowerShell console (version 4.0 or 3.0). You must import the Active Directory module before the topics will be available.
Check out this syntax:
Get-ADUser -Filter 'memberOf -RecursiveMatch "<distinguished name of group>"' -SearchBase "<distinguished name of user>"
Here is an example:
Get-ADUser -Filter 'memberOf ‑RecursiveMatch "CN=Administrators,CN=Builtin,DC=Fabrikam,DC=com"' ‑SearchBase "CN=Administrator,CN=Users,DC=Fabrikam,DC=com"
If the user is a member of the group, the query will return an AD object representing the user. If not a member of the group, the query will return nothing. The beauty is that it does all of the recursive group checking for you.
Now let’s make this more interesting. I really don’t like typing those long Active Directory distinguished names, and it is easier to supply these from a query like this:
Get-ADUser -Filter "memberOf -RecursiveMatch '$((Get-ADGroup "Domain Admins").DistinguishedName)'" -SearchBase $((Get-ADUser Guest).DistinguishedName)
Note We can use a Windows PowerShell variable subexpression $() to retrieve the user and group
distinguished names dynamically and supply them to the filter properties.
Finally, we can make a handy function by adding a couple variables like this:
Function Test-ADGroupMember {
Param ($User,$Group)
Trap {Return "error"}
If (
Get-ADUser `
-Filter "memberOf -RecursiveMatch '$((Get-ADGroup $Group).DistinguishedName)'" `
-SearchBase $((Get-ADUser $User).DistinguishedName)
) {$true}
Else {$false}
}
Now we have a simple function to check if a user is nested into a privileged group:
PS C:\> Test-ADGroupMember -User Guest -Group "Domain Admins"
True
PS C:\> Test-ADGroupMember -User JoeJrAdmin -Group "Domain Admins"
False
PS C:\> Test-ADGroupMember -User bogus -Group "Domain Admins"
error
Uh oh! How did the Guest account get nested into Domain Admins? For that answer, you’ll need to read my post, AD Group History Mystery: PowerShell v3 REPADMIN.
This function performs three queries, so it is not very efficient. If you wanted to process a large number of users, you could try this approach instead:
$Group = "Domain Admins"
$Users = (Get-ADUser -Filter "name -like 'ad*'").DistinguishedName
$Members = (Get-ADGroupMember $Group -Recursive).DistinguishedName
ForEach ($User in $Users) {
[PSCustomObject]@{
User = $User
Group = $Group
IsMember = $Members.Contains($User)
}
}
Adjust the Get-ADUser -Filter parameter to suit your needs. The output looks similar to this:
User Group IsMember
---- ----- --------
CN=adbarr,OU=Migrated,DC=CohoVineyard,DC=com Domain Admins False
CN=adcarter,CN=Users,DC=CohoVineyard,DC=com Domain Admins False
CN=addumitr,CN=Users,DC=CohoVineyard,DC=com Domain Admins False
CN=Administrator,CN=Users,DC=CohoVineyard,DC=com Domain Admins True
This table conveniently summarizes the techniques we have discussed in this blog post.
| Immediate | Nested/Recursive |
Members of a Group | Get-ADGroupMember GroupName | Get‑ADGroupMember GroupName‑Recursive |
Group membership of a User | Get-ADPrincipalGroupMembership UserName | All groups: Reference blog post: Test for one group: Get-ADUser ‑Filter 'memberOf ‑RecursiveMatch "<distinguished name of group>"' ‑SearchBase "<distinguished name of user>" |
Armed with this knowledge, you now have some scripting tools to help you trace group memberships.
~ Ashley
Thanks for this great series, Ashley! Come back tomorrow for Day 3 of Active Directory Week.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy