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 } |
Comments
- Anonymous
February 09, 2010
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. - Anonymous
June 07, 2013
I'm still kind of a powershell n00b. How do you get the reported info to file, and readable? - Anonymous
January 14, 2015
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)? - Anonymous
March 04, 2015
Unfortunately this does not work for me. Always shows maxNestingLevel 0 and no members. - Anonymous
March 04, 2015
Hi,I can't access the script once I cd into the GC.Can you help? - Anonymous
April 17, 2015
fully qualify your filename with complete path