Active Directory Week: Explore Group Membership with PowerShell


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?)

Image of flow chart

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:
Token Bloat Troubleshooting by Analyzing Group Nesting in AD

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. 

Ashley recently recorded a full day of free Active Directory PowerShell training: Microsoft Virtual Academy: Using PowerShell for Active Directory. Watch these videos to learn more insider tips on topics like getting started with Active Directory PowerShell, routine administration, stale accounts, managing replication, disaster recovery, and domain controller deployment.

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 

Comments (9)

  1. Thanks for the tip, Dan!

  2. The ActiveDirectory Module cmdlet Get-ADPrincipalGroupMembership also won’t work with groups that have "/" characters in them. That, and some additional customer requirements about complete recursive membership exploration and optionally following just
    mail enabled groups versus all groups is why I put together this PowerShell script:
    https://gallery.technet.microsoft.com/Get-Object-Group-56e2d037

    Just thought I would share a link to the scrip in case someone wanted to see all groups a user was recursively a member of versus just one. Your way looks to be faster and simpler if they are interested in just one specific group though.

  3. Hi Steve,
    Use Compare-Object to compare the Members property of both groups. Take the differences and give them to Add-ADGroupMember.
    Ashley

  4. Steve says:

    What is the best way to copy all members from one group into another without it erroring out if one of the members already exists in the destination group?

  5. Any ideas how one might use something like this to enumerate the members of a nested structure, and then populate a new group based on that? For flattening or migrated structures……or things like Azure license assignment where we don’t support nested
    groups.

  6. Samuel Ortiz says:

    Thank you

  7. Samuel Ortiz says:

    I could work with a script to expand access to users massively?

  8. Hi Ashley,
    Let me know what you think about this PowerShell script enabling visualization of nested groups :
    https://gallery.technet.microsoft.com/Graph-Nested-AD-Security-eaa01644
    Regards,
    Axel

  9. Willem Verwijs says:

    so how do i get the "member of" for a group. e.g. i have a group called gg_team1 and I want to know the (LG_) groups. I could open gg_team1 in AD U&C and check the "member of" tab, but i want to know this in bulk. any suggestions?

Skip to main content