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:
Usage:
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
cheers,
Dushyant Gill
Program Manager - Microsoft Corporation
##########Copy the below script into a new file called Get-ADGroupNesting.ps1 Param ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage="DN or ObjectGUID of the AD Group." )] [string]$groupIdentity, [switch]$showTree ) $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) { if($showTree) { for($i = 0; $i -lt $level - 1; $i++) { if($lastGroupAtALevelFlags[$i] -ne 0) { Write-Host -ForegroundColor Yellow -NoNewline " " } else { Write-Host -ForegroundColor Yellow -NoNewline "│ " } } if($level -ne 0) { if($lastGroupOfTheLevel) { Write-Host -ForegroundColor Yellow -NoNewline "└─" } else { Write-Host -ForegroundColor Yellow -NoNewline "├─" } } Write-Host -ForegroundColor Yellow $group.Name } $groupsVisitedBeforeThisOne.Add($group.distinguishedName,$null) $global:numberOfRecursiveGroupMemberships ++ $groupMemberShipCount = $group.memberOf.Count if ($groupMemberShipCount -gt 0) { $maxMemberGroupLevel = 0 $count = 0 foreach($groupDN in $group.memberOf) { $count++ $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 if($groupObj) { [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 $groupObj } |