Token Bloat Troubleshooting by Analyzing Group Nesting in AD

This tool started when I was finding ways to analyze the complexity of group memberships in AD. Other than the usual average/median/min/max of number of members, number of memberships etc, I was also interested in finding out the maximum nesting levels of groups and the recursive group membership count. For e.g. in the diagram below, the maximum nesting level of ‘group a’ is 3 and its recursive group membership count is 6.


Analyzing the recursive group membership of a group is helpful in troubleshooting many scenarios, for e.g. Token Bloat troubleshooting/monitoring, misdirected distribution group memberships etc…

The attached script (Get-ADGroupNesting) retrieves an AD group with 2 additional properties:

1. NestedGroupMembershipCount

2. MaxNestingLevel


When used with the –ShowTree parameter, the script displays the recursive group membership tree along with emitting the ADGroup object.


The script can be used with Get-ADPrincipalGroupMembership cmdlet to analyze the recursive group membership of a user/computer/group.


The above usage can be filtered to analyze the recursive group membership of security groups only, by adding a Where clause … This usage can be utilized in troubleshooting token bloat issues.


Finally, a sample of usage with Get-ADUser cmdlet:



Step 1 (Important): Map a new AD PowerShell Provider drive to the Global Catalog. And CD to it.

PS C:\> New-PSDrive -PSProvider ActiveDirectory -Server <dc/domain name> -Root "" –GlobalCatalog –Name GC

PS C:\> cd GC:

PS GC:\>

Step2: Use the script Get-ADGroupNesting.ps1 in the below ways.

Here’ the commands in the above screenshots:

1. PS GC:\> Get-ADGroupNesting.ps1 CarAnnounce

2. PS GC:\> Get-ADGroupNesting.ps1 CarAnnounce –ShowTree

3. PS GC:\> Get-ADPrincipalGroupMembership DonFu | % {Get-ADGroupNesting $_} | FT Name,GroupCategory,NestedGroupMembershipCount,MaxNestingLevel –A

4. PS GC:\> Get-ADPrincipalGroupMembership DonFu | Where {$_.GroupCategory -eq "Security"} | % {Get-ADGroupNesting $_ -ShowTree | FT Name,GroupCategory,NestedGroupMembershipCount,MaxNestingLevel -A}

5. PS GC:\> (Get-ADUser DonFu -Properties MemberOf).MemberOf | % {Get-ADGroupNesting.ps1 $_ -ShowTree} | FL DistinguishedName,NestedGroupMembershipCount,MaxNestingLevel



Dushyant Gill

Program Manager - Microsoft Corporation


##########Copy the below script into a new file called Get-ADGroupNesting.ps1

Param (
        HelpMessage="DN or ObjectGUID of the AD Group."

$global:numberOfRecursiveGroupMemberships = 0
$lastGroupAtALevelFlags = @()

function Get-GroupNesting ([string] $identity, [int] $level, [hashtable] $groupsVisitedBeforeThisOne, [bool] $lastGroupOfTheLevel)
    $group = $null
    $group = Get-ADGroup -Identity $identity -Properties "memberOf"   
    if($lastGroupAtALevelFlags.Count -le $level)
        $lastGroupAtALevelFlags = $lastGroupAtALevelFlags + 0
    if($group -ne $null)
            for($i = 0; $i -lt $level - 1; $i++)
                if($lastGroupAtALevelFlags[$i] -ne 0)
                    Write-Host -ForegroundColor Yellow -NoNewline "  "
                    Write-Host -ForegroundColor Yellow -NoNewline "│ "
            if($level -ne 0)
                    Write-Host -ForegroundColor Yellow -NoNewline "└─"
                    Write-Host -ForegroundColor Yellow -NoNewline "├─"
            Write-Host -ForegroundColor Yellow $group.Name
        $global:numberOfRecursiveGroupMemberships ++
        $groupMemberShipCount = $group.memberOf.Count
        if ($groupMemberShipCount -gt 0)
            $maxMemberGroupLevel = 0
            $count = 0
            foreach($groupDN in $group.memberOf)
                $lastGroupOfThisLevel = $false
                if($count -eq $groupMemberShipCount){$lastGroupOfThisLevel = $true; $lastGroupAtALevelFlags[$level] = 1}
                if(-not $groupsVisitedBeforeThisOne.Contains($groupDN)) #prevent cyclic dependancies
                    $memberGroupLevel = Get-GroupNesting -Identity $groupDN -Level $($level+1) -GroupsVisitedBeforeThisOne $groupsVisitedBeforeThisOne -lastGroupOfTheLevel $lastGroupOfThisLevel
                    if ($memberGroupLevel -gt $maxMemberGroupLevel){$maxMemberGroupLevel = $memberGroupLevel}
            $level = $maxMemberGroupLevel
        else #we've reached the top level group, return it's height
            return $level
        return $level
$global:numberOfRecursiveGroupMemberships = 0
$groupObj = $null
$groupObj = Get-ADGroup -Identity $groupIdentity
    [int]$maxNestingLevel = Get-GroupNesting -Identity $groupIdentity -Level 0 -GroupsVisitedBeforeThisOne @{} -lastGroupOfTheLevel $false
    Add-Member -InputObject $groupObj -MemberType NoteProperty  -Name MaxNestingLevel -Value $maxNestingLevel -Force
    Add-Member -InputObject $groupObj -MemberType NoteProperty  -Name NestedGroupMembershipCount -Value $($global:numberOfRecursiveGroupMemberships - 1) -Force

Comments (6)

  1. ekilleen says:

    Thanks for posting this, group hijacking is a pretty big problem for distribution lists and security groups.  This can happen to a user without them even being aware.

  2. John says:

    I'm still kind of a powershell n00b.  How do you get the reported info to file, and readable?

  3. output question says:

    I have a lot of groups I want to look at and out-file/ export the results…. For a modification what is the recommended way to import from csv (group names) and out-file, export to a txt file (or another file) that will still maintain the tree (the command from step 2,  #2)?

  4. Trix says:

    Unfortunately this does not work for me. Always shows maxNestingLevel 0 and no members.

  5. Frank-Ove says:


    I can't access the script once I cd into the GC.

    Can you help?

  6. AndreB says:

    fully qualify your filename with complete path

Skip to main content