A few pitfalls exist if you try to read the members of nested Active Directory groups with the PowerShell cmdlet Get-ADGroupMember and the -recursive parameter.

You don't have to work with Active Directory groups for very long before you can see how they become complicated, sprawling messes, especially once you start managing nested groups. When we say "nested group" we are referring to the Active Directory groups in your organization that have groups embedded within them.

It doesn't have to stop there, you know. You can have groups within groups within groups and so on. Because of this, using simple one-liners on your groups to find their true membership details may not be enough. We have to dig deeper to find out who actually belongs in which group.

Setting up Active Directory ^

I'm sure if you're tuning in to read about Active Directory concepts, you already have some experience with Active Directory and have the tools installed on your machine. In case you don't, you can follow this handy guide for the full instructions. But at a high level, you'll need the following:

  • Remote Server Administration Tools—specifically, you'll need the Active Directory Domain Services (AD DS) and AD Lightweight Directory Services (LDS) tools installed.
  • The Active Directory module, which you can import with a simple Import-Module ActiveDirectory.

To simplify our demo, I'm also going to set up three Active Directory groups. Our first group will be called Top and will be the parent of all the other groups. Our next group will be called Middle, and our last group will be called Bottom. To make our group memberships look as nested as possible, all the members of Bottom will be in Middle, and all the members of Middle will be in Top. I'll also sprinkle a few other members that aren't nested into each group.

Top group properties

Top group properties

Middle group properties

Middle group properties

Bottom group properties

Bottom group properties

Get members of a group ^

We can look at members of a group by using the Get-ADGroupMember cmdlet. This cmdlet is useful for a couple of reasons. If we wanted to query each group individually, we could simply perform the following query and retrieve all the users in a single group. In my case, I'll query my Top group to see what all we get back:

As we can see, PowerShell responds back with all of the members of our Top group, which includes our Top User and the Middle group.

Get members of child groups ^

We can also get a recursive result using the Get-ADGroupMember cmdlet. By simply adding the -recursive flag on the cmdlet, PowerShell will enumerate each group and return a full result with all of their members. From here you could do any of your regular filtering and formatting to return precisely what you want.

Recursive result

Recursive result

There are only a few pitfalls to using the -recursive flag, but depending on your script, they may be pretty detrimental. One secret I haven't let on yet is that our user Second User is actually disabled. What if we were supposed to be scripting against only enabled users? Disabled users? Also, what if we were supposed to preserve the group names in our script? I'll show you how to do each of these below.

Returning only enabled or disabled users

What if, instead of all users available, we needed only the disabled or enabled users? We can filter our commands a few ways to do this. The simplest and easiest, however, is to use the pipe to create a list of the appropriate users. For finding the enabled users, we can modify our command as follows:

The command above would return all of our users, with the exception of our user02 disabled user. Similarly, for disabled users, you can simply switch the filter for the Enabled property to $false:

Returning all group names

It's useful to have the parent group name of the members we are querying. If we want to keep these names returned with our object, we have to do a bit of custom scripting to accomplish this. In the script below, we'll not only grab the enabled users, but then we'll perform some additional filtering to return information about the user on a single line.

From this result, you could filter further to see all the groups in common. This is especially useful during account cleanups and audits.

Summary ^

As you can see, the Get-ADGroupMember cmdlet is a highly useful one. We covered how to query an individual group's members and how to get the nested members of all groups. The fun doesn't end here though, since you could easily take the output of these commands and throw them into your next one. You could modify these users, put them in a CSV, or even create your own custom object and allow manipulating them later. The choice is yours with PowerShell!

Join the 4sysops PowerShell group!

Your question was not answered? Ask in the forum!

  1. Jim Lovejoy 2 years ago

    PS C:\WINDOWS\system32> Get-ADGroupMember -Identity top -Recursive | `
    Get-ADUser -Filter {Enabled -eq $true} | ForEach-Object {
    New-Object PSObject -Property @{
    UserName = $_.DisplayName
    AccountName = $_.SamAccountName
    Groups = ($_.memberof | Get-ADGroup | (Select-Object -ExpandProperty Name) -join ","}
    } | Select-Object UserName,AccountName,Groups

    At line:6 char:47
    + ... berof | Get-ADGroup | (Select-Object -ExpandProperty Name) -join ","}
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Expressions are only allowed as the first element of a pipeline.
    At line:6 char:93
    + ... berof | Get-ADGroup | (Select-Object -ExpandProperty Name) -join ","}
    + ~
    Missing closing ')' in expression.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ExpressionsMustBeFirstInPipeline


    Users who have LIKED this comment:

    • avatar
    • Author

      You are getting this error because there are multiple groups returned from the "Get-ADGroup" cmdlet on line 6.  You can fix this by placing a "ForEach-Object" after the pipe, so line 6 will end up looking like this:

       Groups = ($_.memberof | Get-ADGroup | ForEach-Object (Select-Object -ExpandProperty Name) -join ",")

      Note: I also made the final character a close parenthesis ")" rather than a bracket "}".  I think this might have been an error in uploading the article to the site, sorry about that!


  2. Alex Ø. T. Hansen 2 years ago

    The -recursive method doesn't always work.
    I have created a function that does basically the same.


    Users who have LIKED this comment:

    • avatar
    • Author

      Very nice!  Would you mind sharing your function, or is it published on GitHub or other version control system?

      In my experience, the -recursive method hasn't failed.  Could you describe an instance where it had?  I'd like to be able to account for failures, but I wasn't aware that this was an iffy parameter.



      • Alex Ø. T. Hansen 2 years ago

        If you want you can just “steal” the function 😉


        Users who have LIKED this comment:

        • avatar
  3. Guilherme Rusth 2 years ago

    for my script i just need to know the name of the nested group members and if they are enable or disable can you help me ?


  4. Tim 10 months ago

    Hi Bryce,

    could you expand your article for the case of "copying all groups with their members(groups and user) to another organisation unit"

    So the result is a clone of a organisation unit with all its groups, subgroups and users.

    Best regards,


Leave a reply

Your email address will not be published. Required fields are marked *


© 4sysops 2006 - 2020


Please ask IT administration questions in the forums. Any other messages are welcome.


Log in with your credentials


Forgot your details?

Create Account