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.

Image1

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

Image2

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

Image3

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

Image4

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.

Image5

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

Image6

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
}