[{"data":1,"prerenderedAt":3084},["ShallowReactive",2],{"post:\u002F2025\u002F12\u002F03\u002Fpowershell-active-directory-report-group-membership-sprawl\u002F":3},{"post":4,"newer":3035,"older":3047,"related":3056,"series":3082},{"id":5,"title":6,"body":7,"canonical":3016,"categories":3017,"date":3020,"description":3021,"extension":3022,"featured":3023,"hero":3024,"image":3024,"meta":3025,"navigation":991,"path":3026,"readingTime":547,"seo":3027,"series":3024,"seriesOrder":3024,"sites":3028,"source":3024,"stem":3029,"tags":3030,"updated":3024,"url":3033,"__hash__":3034},"blog\u002Fblog\u002F2025\u002F12\u002F03\u002Fpowershell-active-directory-report-group-membership-sprawl.md","PowerShell: Active Directory – Report Group Membership Sprawl",{"type":8,"value":9,"toc":3008},"minimark",[10,14,27,32,60,64,188,192,195,216,219,258,261,269,272,413,446,710,716,720,2874,2878,2961,2965,3004],[11,12,13],"p",{},"Every Active Directory environment I have ever inherited has the same problem lurking in it: security groups nobody remembers creating, nested three or four levels deep, granting access that nobody can explain in an access review. This script doesn't fix group sprawl, but it produces the report you need to start the conversation: every security group ranked by how deep its nesting goes, how many accounts it really reaches once nesting is expanded, whether anyone owns it, and whether it has been touched in the last year.",[11,15,16,17,21,22,26],{},"Sprawl isn't only an audit problem. Every group a user belongs to, directly or through nesting, adds a SID to the user's Kerberos ticket, and Microsoft documents the resulting failure mode: users in too many groups get ",[18,19,20],"strong",{},"HTTP 400 - Bad Request (Request Header too long)"," from IIS-hosted apps, have trouble reaching resources, and may not get Group Policy updates. The default ",[23,24,25],"code",{},"MaxTokenSize"," is 48,000 bytes on Windows Server 2012 \u002F Windows 8 and later, and there is a separate hard limit of 1,010 group SIDs in an access token. Deep nesting is how ordinary users drift toward those limits without anyone adding them to hundreds of groups by hand, so the Usage section below also includes a per-user token size estimate.",[28,29,31],"h2",{"id":30},"requirements","Requirements",[33,34,35,39,57],"ul",{},[36,37,38],"li",{},"Windows PowerShell 5.1 or PowerShell 7 on Windows, with the ActiveDirectory module (RSAT).",[36,40,41,42,45,46,45,49,52,53,56],{},"An account that can read group objects and their ",[23,43,44],{},"member",", ",[23,47,48],{},"memberOf",[23,50,51],{},"managedBy"," and ",[23,54,55],{},"whenChanged"," attributes across the domain.",[36,58,59],{},"A DC running Active Directory Web Services, which is what the module talks to.",[28,61,63],{"id":62},"parameters","Parameters",[65,66,67,86],"table",{},[68,69,70],"thead",{},[71,72,73,77,80,83],"tr",{},[74,75,76],"th",{},"Name",[74,78,79],{},"Type",[74,81,82],{},"Required",[74,84,85],{},"Description",[87,88,89,106,128,145,160,174],"tbody",{},[71,90,91,97,100,103],{},[92,93,94],"td",{},[23,95,96],{},"SearchBase",[92,98,99],{},"String",[92,101,102],{},"No",[92,104,105],{},"Distinguished name of the OU whose groups are reported. Defaults to the whole domain. Nesting is always resolved against every group in the domain, so a group outside the OU still counts toward depth.",[71,107,108,113,116,118],{},[92,109,110],{},[23,111,112],{},"StaleDays",[92,114,115],{},"Int",[92,117,102],{},[92,119,120,121,123,124,127],{},"Groups whose ",[23,122,55],{}," is older than this many days are flagged as stale. Defaults to ",[23,125,126],{},"365",".",[71,129,130,135,137,139],{},[92,131,132],{},[23,133,134],{},"MaxNestingDepth",[92,136,115],{},[92,138,102],{},[92,140,141,142,127],{},"Nesting depth at or above which a group is flagged as deeply nested. Defaults to ",[23,143,144],{},"3",[71,146,147,152,155,157],{},[92,148,149],{},[23,150,151],{},"IncludeDistributionGroups",[92,153,154],{},"Switch",[92,156,102],{},[92,158,159],{},"Also report distribution groups. By default only security groups are reported, since only they end up in access tokens.",[71,161,162,167,169,171],{},[92,163,164],{},[23,165,166],{},"OutputPath",[92,168,99],{},[92,170,102],{},[92,172,173],{},"CSV path to export the full report to. If omitted, the report only prints to the console.",[71,175,176,181,183,185],{},[92,177,178],{},[23,179,180],{},"Server",[92,182,99],{},[92,184,102],{},[92,186,187],{},"Domain controller to run every query against. Defaults to whichever DC the module discovers.",[28,189,191],{"id":190},"usage","Usage",[11,193,194],{},"Report on the whole domain with default thresholds:",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-powershell shiki shiki-themes github-dark",".\\Get-GroupSprawlReport.ps1\n","powershell","",[23,203,204],{"__ignoreMap":201},[205,206,209,212],"span",{"class":207,"line":208},"line",1,[205,210,127],{"class":211},"s95oV",[205,213,215],{"class":214},"sDLfK","\\Get-GroupSprawlReport.ps1\n",[11,217,218],{},"Scope to one OU, flag anything untouched in two years, and export for a quarterly access review:",[196,220,222],{"className":198,"code":221,"language":200,"meta":201,"style":201},".\\Get-GroupSprawlReport.ps1 -SearchBase 'OU=Groups,DC=corp,DC=example,DC=com' -StaleDays 730 -OutputPath 'C:\\Reports\\group-sprawl.csv'\n",[23,223,224],{"__ignoreMap":201},[205,225,226,228,231,235,238,242,244,247,250,252,255],{"class":207,"line":208},[205,227,127],{"class":211},[205,229,230],{"class":214},"\\Get-GroupSprawlReport.ps1",[205,232,234],{"class":233},"snl16"," -",[205,236,237],{"class":211},"SearchBase ",[205,239,241],{"class":240},"sU2Wk","'OU=Groups,DC=corp,DC=example,DC=com'",[205,243,234],{"class":233},[205,245,246],{"class":211},"StaleDays ",[205,248,249],{"class":214},"730",[205,251,234],{"class":233},[205,253,254],{"class":211},"OutputPath ",[205,256,257],{"class":240},"'C:\\Reports\\group-sprawl.csv'\n",[11,259,260],{},"Sample output, sorted by nesting depth and then transitive member count:",[196,262,267],{"className":263,"code":265,"language":266,"meta":201},[264],"language-text","Loaded 1,842 groups from DC=corp,DC=example,DC=com. Reporting 1,317 group(s) under OU=Groups,DC=corp,DC=example,DC=com.\n\nName                      Scope       NestingDepth ChildGroups DirectMembers TransitiveMembers DaysSinceChanged HasManagedBy Flags\n----                      -----       ------------ ----------- ------------- ----------------- ---------------- ------------ -----\nGG-AllStaff-Legacy        Global                 5           7             7              2210              612 False        Stale,DeeplyNested,NoManagedBy\nDL-FS01-Projects-Modify   DomainLocal            4           3             3               842               45 True         DeeplyNested\nGG-Finance-Contractors    Global                 3           2            14                63             1094 False        Stale,DeeplyNested,NoManagedBy\nUG-App-Reporting          Universal              2           1             1                 0              401 False        Stale,Empty,NoManagedBy\nGG-Loop-A                 Global                 1           1             1                12              220 True         Circular\n","text",[23,268,265],{"__ignoreMap":201},[11,270,271],{},"Find the groups that most often sit in the middle of chains (the ones worth flattening first) from the exported CSV:",[196,273,275],{"className":198,"code":274,"language":200,"meta":201,"style":201},"Import-Csv -Path 'C:\\Reports\\group-sprawl.csv' |\n    Where-Object { [int]$_.NestedInCount -gt 0 -and [int]$_.ChildGroups -gt 0 } |\n    Sort-Object -Property { [int]$_.NestedInCount } -Descending |\n    Select-Object -First 20 -Property Name, NestedInCount, ChildGroups, NestingDepth, TransitiveMembers\n",[23,276,277,293,345,373],{"__ignoreMap":201},[205,278,279,282,284,287,290],{"class":207,"line":208},[205,280,281],{"class":214},"Import-Csv",[205,283,234],{"class":233},[205,285,286],{"class":211},"Path ",[205,288,289],{"class":240},"'C:\\Reports\\group-sprawl.csv'",[205,291,292],{"class":233}," |\n",[205,294,296,299,302,305,308,311,314,317,320,323,326,328,330,332,335,337,339,342],{"class":207,"line":295},2,[205,297,298],{"class":214},"    Where-Object",[205,300,301],{"class":211}," { [",[205,303,304],{"class":233},"int",[205,306,307],{"class":211},"]",[205,309,310],{"class":214},"$_",[205,312,313],{"class":211},".NestedInCount ",[205,315,316],{"class":233},"-gt",[205,318,319],{"class":214}," 0",[205,321,322],{"class":233}," -and",[205,324,325],{"class":211}," [",[205,327,304],{"class":233},[205,329,307],{"class":211},[205,331,310],{"class":214},[205,333,334],{"class":211},".ChildGroups ",[205,336,316],{"class":233},[205,338,319],{"class":214},[205,340,341],{"class":211}," } ",[205,343,344],{"class":233},"|\n",[205,346,348,351,353,356,358,360,362,365,368,371],{"class":207,"line":347},3,[205,349,350],{"class":214},"    Sort-Object",[205,352,234],{"class":233},[205,354,355],{"class":211},"Property { [",[205,357,304],{"class":233},[205,359,307],{"class":211},[205,361,310],{"class":214},[205,363,364],{"class":211},".NestedInCount } ",[205,366,367],{"class":233},"-",[205,369,370],{"class":211},"Descending ",[205,372,344],{"class":233},[205,374,376,379,381,384,387,389,392,395,398,400,403,405,408,410],{"class":207,"line":375},4,[205,377,378],{"class":214},"    Select-Object",[205,380,234],{"class":233},[205,382,383],{"class":211},"First ",[205,385,386],{"class":214},"20",[205,388,234],{"class":233},[205,390,391],{"class":211},"Property Name",[205,393,394],{"class":233},",",[205,396,397],{"class":211}," NestedInCount",[205,399,394],{"class":233},[205,401,402],{"class":211}," ChildGroups",[205,404,394],{"class":233},[205,406,407],{"class":211}," NestingDepth",[205,409,394],{"class":233},[205,411,412],{"class":211}," TransitiveMembers\n",[11,414,415,416,419,420,423,424,427,428,431,432,435,436,439,440,442,443,445],{},"Estimate one user's Kerberos token size with Microsoft's formula, ",[23,417,418],{},"TokenSize = 1200 + 40d + 8s",", where, on Windows Server 2012 and later, ",[23,421,422],{},"d"," counts universal groups outside the user's account domain plus the SIDs in ",[23,425,426],{},"sIDHistory",", and ",[23,429,430],{},"s"," counts universal groups inside the account domain plus every global and domain-local group. The snippet sorts SIDs by domain prefix, which lines up with those classes for ",[23,433,434],{},"tokenGroups",": the only SIDs from other domains it carries are universal groups (global groups can't hold members from another domain, and other domains' domain-local groups are added by the resource domain, not stored here), while built-in ",[23,437,438],{},"S-1-5-32-"," groups are domain-local and count as ",[23,441,430],{},". It reads the computed ",[23,444,434],{}," attribute, which Microsoft warns is expensive for DCs, so run it for the handful of users you're investigating, not in a loop over the whole directory:",[196,447,449],{"className":198,"code":448,"language":200,"meta":201,"style":201},"$user = Get-ADUser -Identity '\u003Csamaccountname>' -Properties tokenGroups, sIDHistory\n$domainSid = (Get-ADDomain).DomainSID.Value\n$groupSids = @($user.tokenGroups | Where-Object { $_ })\n$historySids = @($user.sIDHistory | Where-Object { $_ })\n$sameDomain = @($groupSids | Where-Object { $_.Value.StartsWith(\"$domainSid-\") -or $_.Value.StartsWith(\"S-1-5-32-\") }).Count\n$otherDomain = ($groupSids.Count - $sameDomain) + $historySids.Count\n[PSCustomObject]@{\n    User            = $user.SamAccountName\n    GroupSids       = $groupSids.Count\n    EstimatedBytes  = 1200 + (40 * $otherDomain) + (8 * $sameDomain)\n    DefaultMaxBytes = 48000\n}\n",[23,450,451,480,496,523,545,595,617,634,645,656,693,704],{"__ignoreMap":201},[205,452,453,456,459,462,464,467,470,472,475,477],{"class":207,"line":208},[205,454,455],{"class":211},"$user ",[205,457,458],{"class":233},"=",[205,460,461],{"class":214}," Get-ADUser",[205,463,234],{"class":233},[205,465,466],{"class":211},"Identity ",[205,468,469],{"class":240},"'\u003Csamaccountname>'",[205,471,234],{"class":233},[205,473,474],{"class":211},"Properties tokenGroups",[205,476,394],{"class":233},[205,478,479],{"class":211}," sIDHistory\n",[205,481,482,485,487,490,493],{"class":207,"line":295},[205,483,484],{"class":211},"$domainSid ",[205,486,458],{"class":233},[205,488,489],{"class":211}," (",[205,491,492],{"class":214},"Get-ADDomain",[205,494,495],{"class":211},").DomainSID.Value\n",[205,497,498,501,503,506,509,512,515,518,520],{"class":207,"line":347},[205,499,500],{"class":211},"$groupSids ",[205,502,458],{"class":233},[205,504,505],{"class":233}," @",[205,507,508],{"class":211},"($user.tokenGroups ",[205,510,511],{"class":233},"|",[205,513,514],{"class":214}," Where-Object",[205,516,517],{"class":211}," { ",[205,519,310],{"class":214},[205,521,522],{"class":211}," })\n",[205,524,525,528,530,532,535,537,539,541,543],{"class":207,"line":375},[205,526,527],{"class":211},"$historySids ",[205,529,458],{"class":233},[205,531,505],{"class":233},[205,533,534],{"class":211},"($user.sIDHistory ",[205,536,511],{"class":233},[205,538,514],{"class":214},[205,540,517],{"class":211},[205,542,310],{"class":214},[205,544,522],{"class":211},[205,546,548,551,553,555,558,560,562,564,566,569,572,575,578,581,584,587,589,592],{"class":207,"line":547},5,[205,549,550],{"class":211},"$sameDomain ",[205,552,458],{"class":233},[205,554,505],{"class":233},[205,556,557],{"class":211},"($groupSids ",[205,559,511],{"class":233},[205,561,514],{"class":214},[205,563,517],{"class":211},[205,565,310],{"class":214},[205,567,568],{"class":211},".Value.StartsWith(",[205,570,571],{"class":240},"\"",[205,573,574],{"class":211},"$domainSid",[205,576,577],{"class":240},"-\"",[205,579,580],{"class":211},") ",[205,582,583],{"class":233},"-or",[205,585,586],{"class":214}," $_",[205,588,568],{"class":211},[205,590,591],{"class":240},"\"S-1-5-32-\"",[205,593,594],{"class":211},") }).Count\n",[205,596,598,601,603,606,608,611,614],{"class":207,"line":597},6,[205,599,600],{"class":211},"$otherDomain ",[205,602,458],{"class":233},[205,604,605],{"class":211}," ($groupSids.Count ",[205,607,367],{"class":233},[205,609,610],{"class":211}," $sameDomain) ",[205,612,613],{"class":233},"+",[205,615,616],{"class":211}," $historySids.Count\n",[205,618,620,623,626,628,631],{"class":207,"line":619},7,[205,621,622],{"class":211},"[",[205,624,625],{"class":233},"PSCustomObject",[205,627,307],{"class":211},[205,629,630],{"class":233},"@",[205,632,633],{"class":211},"{\n",[205,635,637,640,642],{"class":207,"line":636},8,[205,638,639],{"class":211},"    User            ",[205,641,458],{"class":233},[205,643,644],{"class":211}," $user.SamAccountName\n",[205,646,648,651,653],{"class":207,"line":647},9,[205,649,650],{"class":211},"    GroupSids       ",[205,652,458],{"class":233},[205,654,655],{"class":211}," $groupSids.Count\n",[205,657,659,662,664,667,670,672,675,678,681,683,685,688,690],{"class":207,"line":658},10,[205,660,661],{"class":211},"    EstimatedBytes  ",[205,663,458],{"class":233},[205,665,666],{"class":214}," 1200",[205,668,669],{"class":233}," +",[205,671,489],{"class":211},[205,673,674],{"class":214},"40",[205,676,677],{"class":233}," *",[205,679,680],{"class":211}," $otherDomain) ",[205,682,613],{"class":233},[205,684,489],{"class":211},[205,686,687],{"class":214},"8",[205,689,677],{"class":233},[205,691,692],{"class":211}," $sameDomain)\n",[205,694,696,699,701],{"class":207,"line":695},11,[205,697,698],{"class":211},"    DefaultMaxBytes ",[205,700,458],{"class":233},[205,702,703],{"class":214}," 48000\n",[205,705,707],{"class":207,"line":706},12,[205,708,709],{"class":211},"}\n",[196,711,714],{"className":712,"code":713,"language":266,"meta":201},[264],"User     GroupSids EstimatedBytes DefaultMaxBytes\n----     --------- -------------- ---------------\n\u003Cuser>         214           2912           48000\n",[23,715,713],{"__ignoreMap":201},[28,717,719],{"id":718},"script","Script",[196,721,723],{"className":198,"code":722,"language":200,"meta":201,"style":201},"\u003C#\n.SYNOPSIS\n    Reports Active Directory groups by nesting depth, transitive membership, ownership and staleness.\n.DESCRIPTION\n    Loads every group in the domain once (member, memberOf, managedBy, whenChanged) and builds an\n    in-memory nesting graph, so nesting depth is calculated without one directory round trip per\n    child group and nesting that crosses the SearchBase boundary is still followed. For each group\n    under SearchBase it then counts transitive non-group members with a single server-side\n    LDAP_MATCHING_RULE_IN_CHAIN query instead of repeated Get-ADGroupMember -Recursive calls. Groups are\n    flagged as Empty, Stale, DeeplyNested, NoManagedBy or Circular. Intended as the worklist for an access\n    review, not an automatic cleanup.\n.PARAMETER SearchBase\n    Distinguished name of the OU whose groups are reported. Defaults to the whole domain.\n.PARAMETER StaleDays\n    Groups whose whenChanged is older than this many days are flagged as stale.\n.PARAMETER MaxNestingDepth\n    Nesting depth at or above which a group is flagged as deeply nested.\n.PARAMETER IncludeDistributionGroups\n    Also report distribution groups. By default only security groups are reported.\n.PARAMETER OutputPath\n    CSV path to export the full report to. If omitted, the report only prints to the console.\n.PARAMETER Server\n    Domain controller to run every query against. Defaults to whichever DC the module discovers.\n.EXAMPLE\n    .\\Get-GroupSprawlReport.ps1 -SearchBase 'OU=Groups,DC=corp,DC=example,DC=com' -StaleDays 730\n.NOTES\n    Author  : Thomas Lasswell (https:\u002F\u002Fwww.techcolumnist.com)\n    Version : 1.0 (2025-12-03)\n    Requires: ActiveDirectory PowerShell module (RSAT)\n#>\n[CmdletBinding()]\nparam (\n    [Parameter(Mandatory = $false)]\n    [string]$SearchBase,\n\n    [Parameter(Mandatory = $false)]\n    [int]$StaleDays = 365,\n\n    [Parameter(Mandatory = $false)]\n    [int]$MaxNestingDepth = 3,\n\n    [Parameter(Mandatory = $false)]\n    [switch]$IncludeDistributionGroups,\n\n    [Parameter(Mandatory = $false)]\n    [string]$OutputPath,\n\n    [Parameter(Mandatory = $false)]\n    [string]$Server\n)\n\nImport-Module ActiveDirectory -ErrorAction Stop\n\n# Splatted into every AD query so a -Server value pins the whole run to one DC.\n$adParams = @{}\nif ($Server) {\n    $adParams['Server'] = $Server\n}\n\n$domainDN = (Get-ADDomain @adParams).DistinguishedName\nif (-not $SearchBase) {\n    $SearchBase = $domainDN\n}\n\n# Load every group in the domain once; nesting is resolved from this map, not from repeated queries.\n$allGroups = Get-ADGroup -Filter * -SearchBase $domainDN -Properties member, memberOf, managedBy, whenChanged @adParams\n$groupsByDN = @{}\nforeach ($group in $allGroups) {\n    $groupsByDN[$group.DistinguishedName] = $group\n}\n\n$depthCache = @{}\n$circular = New-Object -TypeName 'System.Collections.Generic.HashSet[string]'\n\nfunction Get-NestingDepth {\n    param (\n        [Parameter(Mandatory = $true)]\n        [string]$DistinguishedName,\n\n        [Parameter(Mandatory = $true)]\n        [System.Collections.Generic.HashSet[string]]$Path\n    )\n\n    if ($depthCache.ContainsKey($DistinguishedName)) {\n        return $depthCache[$DistinguishedName]\n    }\n\n    # Already on the current path: a circular nest. Record it and stop descending.\n    if (-not $Path.Add($DistinguishedName)) {\n        [void]$circular.Add($DistinguishedName)\n        return 0\n    }\n\n    $maxDepth = 0\n    foreach ($memberDN in $groupsByDN[$DistinguishedName].member) {\n        if ($groupsByDN.ContainsKey($memberDN)) {\n            $childDepth = 1 + (Get-NestingDepth -DistinguishedName $memberDN -Path $Path)\n            if ($childDepth -gt $maxDepth) {\n                $maxDepth = $childDepth\n            }\n        }\n    }\n\n    [void]$Path.Remove($DistinguishedName)\n    $depthCache[$DistinguishedName] = $maxDepth\n    return $maxDepth\n}\n\nfunction Get-ValueCount {\n    param ($Value)\n\n    # An unset attribute can come back as $null, and @($null).Count is 1, not 0.\n    if ($null -eq $Value) {\n        return 0\n    }\n    return @($Value).Count\n}\n\nfunction ConvertTo-LdapFilterValue {\n    param ([string]$Value)\n\n    # RFC 4515 escaping; the backslash must be replaced first.\n    return $Value.Replace('\\', '\\5c').Replace('*', '\\2a').Replace('(', '\\28').Replace(')', '\\29').Replace([string][char]0, '\\00')\n}\n\n$scopeSuffix = ',' + $SearchBase\n$reportGroups = @($allGroups | Where-Object {\n    ($_.DistinguishedName -eq $SearchBase -or $_.DistinguishedName.EndsWith($scopeSuffix, [System.StringComparison]::OrdinalIgnoreCase)) -and\n    ($IncludeDistributionGroups -or $_.GroupCategory -eq 'Security')\n})\n\nWrite-Host (\"Loaded {0:N0} groups from {1}. Reporting {2:N0} group(s) under {3}.\" -f $allGroups.Count, $domainDN, $reportGroups.Count, $SearchBase)\n\n$now = Get-Date\n$cutoffDate = $now.AddDays(-$StaleDays)\n$index = 0\n\n$report = foreach ($group in $reportGroups) {\n    $index++\n    Write-Progress -Activity 'Analysing groups' -Status $group.Name -PercentComplete (($index \u002F $reportGroups.Count) * 100)\n\n    $depth = Get-NestingDepth -DistinguishedName $group.DistinguishedName -Path (New-Object -TypeName 'System.Collections.Generic.HashSet[string]')\n    $childGroups = @($group.member | Where-Object { $_ -and $groupsByDN.ContainsKey($_) }).Count\n    $directMembers = Get-ValueCount -Value $group.member\n\n    # Every non-group object that is a member at any depth, counted on the DC in one query.\n    $chainFilter = '(&(!(objectClass=group))(memberOf:1.2.840.113556.1.4.1941:={0}))' -f (ConvertTo-LdapFilterValue -Value $group.DistinguishedName)\n    $transitive = @(Get-ADObject -LDAPFilter $chainFilter -SearchBase $domainDN @adParams).Count\n\n    $flags = New-Object -TypeName System.Collections.Generic.List[string]\n    if ($group.whenChanged -lt $cutoffDate) { $flags.Add('Stale') }\n    if ($transitive -eq 0) { $flags.Add('Empty') }\n    if ($depth -ge $MaxNestingDepth) { $flags.Add('DeeplyNested') }\n    if (-not $group.managedBy) { $flags.Add('NoManagedBy') }\n    if ($circular.Contains($group.DistinguishedName)) { $flags.Add('Circular') }\n\n    [PSCustomObject]@{\n        Name              = $group.Name\n        Scope             = $group.GroupScope\n        Category          = $group.GroupCategory\n        NestingDepth      = $depth\n        ChildGroups       = $childGroups\n        DirectMembers     = $directMembers\n        TransitiveMembers = $transitive\n        NestedInCount     = Get-ValueCount -Value $group.memberOf\n        DaysSinceChanged  = [int]($now - $group.whenChanged).TotalDays\n        HasManagedBy      = [bool]$group.managedBy\n        Flags             = $flags -join ','\n        DistinguishedName = $group.DistinguishedName\n    }\n}\n\nWrite-Progress -Activity 'Analysing groups' -Completed\n\n$sortedReport = $report | Sort-Object -Property @{ Expression = 'NestingDepth'; Descending = $true }, @{ Expression = 'TransitiveMembers'; Descending = $true }\n\n$sortedReport |\n    Select-Object -First 50 -Property Name, Scope, NestingDepth, ChildGroups, DirectMembers, TransitiveMembers, DaysSinceChanged, HasManagedBy, Flags |\n    Format-Table -AutoSize\n\nif ($OutputPath) {\n    $sortedReport | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8\n    Write-Host \"Full report ($($report.Count) rows) exported to $OutputPath\"\n}\n",[23,724,725,731,738,743,750,755,760,765,770,775,780,785,795,801,811,817,827,833,843,849,859,865,875,881,889,895,903,909,915,921,927,938,947,972,986,993,1010,1027,1032,1049,1066,1071,1088,1101,1106,1123,1135,1140,1157,1167,1173,1178,1192,1197,1203,1216,1225,1242,1247,1252,1267,1280,1291,1296,1301,1307,1351,1363,1378,1389,1394,1399,1411,1430,1435,1448,1456,1475,1487,1492,1509,1524,1530,1535,1544,1553,1559,1564,1570,1582,1593,1601,1606,1611,1621,1635,1644,1672,1686,1697,1703,1709,1714,1719,1729,1740,1748,1753,1758,1768,1776,1781,1787,1803,1810,1815,1825,1830,1835,1845,1858,1863,1869,1939,1944,1949,1965,1984,2021,2041,2047,2052,2084,2089,2100,2116,2126,2131,2149,2158,2195,2200,2232,2262,2277,2282,2288,2311,2336,2341,2361,2381,2401,2420,2437,2450,2455,2468,2479,2490,2501,2512,2523,2534,2545,2560,2580,2596,2613,2624,2629,2634,2639,2656,2661,2722,2727,2734,2790,2801,2806,2814,2840,2869],{"__ignoreMap":201},[205,726,727],{"class":207,"line":208},[205,728,730],{"class":729},"sAwPA","\u003C#\n",[205,732,733,735],{"class":207,"line":295},[205,734,127],{"class":214},[205,736,737],{"class":233},"SYNOPSIS\n",[205,739,740],{"class":207,"line":347},[205,741,742],{"class":729},"    Reports Active Directory groups by nesting depth, transitive membership, ownership and staleness.\n",[205,744,745,747],{"class":207,"line":375},[205,746,127],{"class":214},[205,748,749],{"class":233},"DESCRIPTION\n",[205,751,752],{"class":207,"line":547},[205,753,754],{"class":729},"    Loads every group in the domain once (member, memberOf, managedBy, whenChanged) and builds an\n",[205,756,757],{"class":207,"line":597},[205,758,759],{"class":729},"    in-memory nesting graph, so nesting depth is calculated without one directory round trip per\n",[205,761,762],{"class":207,"line":619},[205,763,764],{"class":729},"    child group and nesting that crosses the SearchBase boundary is still followed. For each group\n",[205,766,767],{"class":207,"line":636},[205,768,769],{"class":729},"    under SearchBase it then counts transitive non-group members with a single server-side\n",[205,771,772],{"class":207,"line":647},[205,773,774],{"class":729},"    LDAP_MATCHING_RULE_IN_CHAIN query instead of repeated Get-ADGroupMember -Recursive calls. Groups are\n",[205,776,777],{"class":207,"line":658},[205,778,779],{"class":729},"    flagged as Empty, Stale, DeeplyNested, NoManagedBy or Circular. Intended as the worklist for an access\n",[205,781,782],{"class":207,"line":695},[205,783,784],{"class":729},"    review, not an automatic cleanup.\n",[205,786,787,789,792],{"class":207,"line":706},[205,788,127],{"class":214},[205,790,791],{"class":233},"PARAMETER",[205,793,794],{"class":233}," SearchBase\n",[205,796,798],{"class":207,"line":797},13,[205,799,800],{"class":729},"    Distinguished name of the OU whose groups are reported. Defaults to the whole domain.\n",[205,802,804,806,808],{"class":207,"line":803},14,[205,805,127],{"class":214},[205,807,791],{"class":233},[205,809,810],{"class":233}," StaleDays\n",[205,812,814],{"class":207,"line":813},15,[205,815,816],{"class":729},"    Groups whose whenChanged is older than this many days are flagged as stale.\n",[205,818,820,822,824],{"class":207,"line":819},16,[205,821,127],{"class":214},[205,823,791],{"class":233},[205,825,826],{"class":233}," MaxNestingDepth\n",[205,828,830],{"class":207,"line":829},17,[205,831,832],{"class":729},"    Nesting depth at or above which a group is flagged as deeply nested.\n",[205,834,836,838,840],{"class":207,"line":835},18,[205,837,127],{"class":214},[205,839,791],{"class":233},[205,841,842],{"class":233}," IncludeDistributionGroups\n",[205,844,846],{"class":207,"line":845},19,[205,847,848],{"class":729},"    Also report distribution groups. By default only security groups are reported.\n",[205,850,852,854,856],{"class":207,"line":851},20,[205,853,127],{"class":214},[205,855,791],{"class":233},[205,857,858],{"class":233}," OutputPath\n",[205,860,862],{"class":207,"line":861},21,[205,863,864],{"class":729},"    CSV path to export the full report to. If omitted, the report only prints to the console.\n",[205,866,868,870,872],{"class":207,"line":867},22,[205,869,127],{"class":214},[205,871,791],{"class":233},[205,873,874],{"class":233}," Server\n",[205,876,878],{"class":207,"line":877},23,[205,879,880],{"class":729},"    Domain controller to run every query against. Defaults to whichever DC the module discovers.\n",[205,882,884,886],{"class":207,"line":883},24,[205,885,127],{"class":214},[205,887,888],{"class":233},"EXAMPLE\n",[205,890,892],{"class":207,"line":891},25,[205,893,894],{"class":729},"    .\\Get-GroupSprawlReport.ps1 -SearchBase 'OU=Groups,DC=corp,DC=example,DC=com' -StaleDays 730\n",[205,896,898,900],{"class":207,"line":897},26,[205,899,127],{"class":214},[205,901,902],{"class":233},"NOTES\n",[205,904,906],{"class":207,"line":905},27,[205,907,908],{"class":729},"    Author  : Thomas Lasswell (https:\u002F\u002Fwww.techcolumnist.com)\n",[205,910,912],{"class":207,"line":911},28,[205,913,914],{"class":729},"    Version : 1.0 (2025-12-03)\n",[205,916,918],{"class":207,"line":917},29,[205,919,920],{"class":729},"    Requires: ActiveDirectory PowerShell module (RSAT)\n",[205,922,924],{"class":207,"line":923},30,[205,925,926],{"class":729},"#>\n",[205,928,930,932,935],{"class":207,"line":929},31,[205,931,622],{"class":211},[205,933,934],{"class":214},"CmdletBinding",[205,936,937],{"class":211},"()]\n",[205,939,941,944],{"class":207,"line":940},32,[205,942,943],{"class":233},"param",[205,945,946],{"class":211}," (\n",[205,948,950,953,956,959,963,966,969],{"class":207,"line":949},33,[205,951,952],{"class":211},"    [",[205,954,955],{"class":214},"Parameter",[205,957,958],{"class":211},"(",[205,960,962],{"class":961},"s9osk","Mandatory",[205,964,965],{"class":233}," =",[205,967,968],{"class":214}," $false",[205,970,971],{"class":211},")]\n",[205,973,975,977,980,983],{"class":207,"line":974},34,[205,976,952],{"class":211},[205,978,979],{"class":233},"string",[205,981,982],{"class":211},"]$SearchBase",[205,984,985],{"class":233},",\n",[205,987,989],{"class":207,"line":988},35,[205,990,992],{"emptyLinePlaceholder":991},true,"\n",[205,994,996,998,1000,1002,1004,1006,1008],{"class":207,"line":995},36,[205,997,952],{"class":211},[205,999,955],{"class":214},[205,1001,958],{"class":211},[205,1003,962],{"class":961},[205,1005,965],{"class":233},[205,1007,968],{"class":214},[205,1009,971],{"class":211},[205,1011,1013,1015,1017,1020,1022,1025],{"class":207,"line":1012},37,[205,1014,952],{"class":211},[205,1016,304],{"class":233},[205,1018,1019],{"class":211},"]$StaleDays ",[205,1021,458],{"class":233},[205,1023,1024],{"class":214}," 365",[205,1026,985],{"class":233},[205,1028,1030],{"class":207,"line":1029},38,[205,1031,992],{"emptyLinePlaceholder":991},[205,1033,1035,1037,1039,1041,1043,1045,1047],{"class":207,"line":1034},39,[205,1036,952],{"class":211},[205,1038,955],{"class":214},[205,1040,958],{"class":211},[205,1042,962],{"class":961},[205,1044,965],{"class":233},[205,1046,968],{"class":214},[205,1048,971],{"class":211},[205,1050,1052,1054,1056,1059,1061,1064],{"class":207,"line":1051},40,[205,1053,952],{"class":211},[205,1055,304],{"class":233},[205,1057,1058],{"class":211},"]$MaxNestingDepth ",[205,1060,458],{"class":233},[205,1062,1063],{"class":214}," 3",[205,1065,985],{"class":233},[205,1067,1069],{"class":207,"line":1068},41,[205,1070,992],{"emptyLinePlaceholder":991},[205,1072,1074,1076,1078,1080,1082,1084,1086],{"class":207,"line":1073},42,[205,1075,952],{"class":211},[205,1077,955],{"class":214},[205,1079,958],{"class":211},[205,1081,962],{"class":961},[205,1083,965],{"class":233},[205,1085,968],{"class":214},[205,1087,971],{"class":211},[205,1089,1091,1093,1096,1099],{"class":207,"line":1090},43,[205,1092,952],{"class":211},[205,1094,1095],{"class":233},"switch",[205,1097,1098],{"class":211},"]$IncludeDistributionGroups",[205,1100,985],{"class":233},[205,1102,1104],{"class":207,"line":1103},44,[205,1105,992],{"emptyLinePlaceholder":991},[205,1107,1109,1111,1113,1115,1117,1119,1121],{"class":207,"line":1108},45,[205,1110,952],{"class":211},[205,1112,955],{"class":214},[205,1114,958],{"class":211},[205,1116,962],{"class":961},[205,1118,965],{"class":233},[205,1120,968],{"class":214},[205,1122,971],{"class":211},[205,1124,1126,1128,1130,1133],{"class":207,"line":1125},46,[205,1127,952],{"class":211},[205,1129,979],{"class":233},[205,1131,1132],{"class":211},"]$OutputPath",[205,1134,985],{"class":233},[205,1136,1138],{"class":207,"line":1137},47,[205,1139,992],{"emptyLinePlaceholder":991},[205,1141,1143,1145,1147,1149,1151,1153,1155],{"class":207,"line":1142},48,[205,1144,952],{"class":211},[205,1146,955],{"class":214},[205,1148,958],{"class":211},[205,1150,962],{"class":961},[205,1152,965],{"class":233},[205,1154,968],{"class":214},[205,1156,971],{"class":211},[205,1158,1160,1162,1164],{"class":207,"line":1159},49,[205,1161,952],{"class":211},[205,1163,979],{"class":233},[205,1165,1166],{"class":211},"]$Server\n",[205,1168,1170],{"class":207,"line":1169},50,[205,1171,1172],{"class":211},")\n",[205,1174,1176],{"class":207,"line":1175},51,[205,1177,992],{"emptyLinePlaceholder":991},[205,1179,1181,1184,1187,1189],{"class":207,"line":1180},52,[205,1182,1183],{"class":214},"Import-Module",[205,1185,1186],{"class":211}," ActiveDirectory ",[205,1188,367],{"class":233},[205,1190,1191],{"class":211},"ErrorAction Stop\n",[205,1193,1195],{"class":207,"line":1194},53,[205,1196,992],{"emptyLinePlaceholder":991},[205,1198,1200],{"class":207,"line":1199},54,[205,1201,1202],{"class":729},"# Splatted into every AD query so a -Server value pins the whole run to one DC.\n",[205,1204,1206,1209,1211,1213],{"class":207,"line":1205},55,[205,1207,1208],{"class":211},"$adParams ",[205,1210,458],{"class":233},[205,1212,505],{"class":233},[205,1214,1215],{"class":211},"{}\n",[205,1217,1219,1222],{"class":207,"line":1218},56,[205,1220,1221],{"class":233},"if",[205,1223,1224],{"class":211}," ($Server) {\n",[205,1226,1228,1231,1234,1237,1239],{"class":207,"line":1227},57,[205,1229,1230],{"class":211},"    $adParams[",[205,1232,1233],{"class":240},"'Server'",[205,1235,1236],{"class":211},"] ",[205,1238,458],{"class":233},[205,1240,1241],{"class":211}," $Server\n",[205,1243,1245],{"class":207,"line":1244},58,[205,1246,709],{"class":211},[205,1248,1250],{"class":207,"line":1249},59,[205,1251,992],{"emptyLinePlaceholder":991},[205,1253,1255,1258,1260,1262,1264],{"class":207,"line":1254},60,[205,1256,1257],{"class":211},"$domainDN ",[205,1259,458],{"class":233},[205,1261,489],{"class":211},[205,1263,492],{"class":214},[205,1265,1266],{"class":211}," @adParams).DistinguishedName\n",[205,1268,1270,1272,1274,1277],{"class":207,"line":1269},61,[205,1271,1221],{"class":233},[205,1273,489],{"class":211},[205,1275,1276],{"class":233},"-not",[205,1278,1279],{"class":211}," $SearchBase) {\n",[205,1281,1283,1286,1288],{"class":207,"line":1282},62,[205,1284,1285],{"class":211},"    $SearchBase ",[205,1287,458],{"class":233},[205,1289,1290],{"class":211}," $domainDN\n",[205,1292,1294],{"class":207,"line":1293},63,[205,1295,709],{"class":211},[205,1297,1299],{"class":207,"line":1298},64,[205,1300,992],{"emptyLinePlaceholder":991},[205,1302,1304],{"class":207,"line":1303},65,[205,1305,1306],{"class":729},"# Load every group in the domain once; nesting is resolved from this map, not from repeated queries.\n",[205,1308,1310,1313,1315,1318,1320,1323,1326,1328,1331,1333,1336,1338,1341,1343,1346,1348],{"class":207,"line":1309},66,[205,1311,1312],{"class":211},"$allGroups ",[205,1314,458],{"class":233},[205,1316,1317],{"class":214}," Get-ADGroup",[205,1319,234],{"class":233},[205,1321,1322],{"class":211},"Filter ",[205,1324,1325],{"class":233},"*",[205,1327,234],{"class":233},[205,1329,1330],{"class":211},"SearchBase $domainDN ",[205,1332,367],{"class":233},[205,1334,1335],{"class":211},"Properties member",[205,1337,394],{"class":233},[205,1339,1340],{"class":211}," memberOf",[205,1342,394],{"class":233},[205,1344,1345],{"class":211}," managedBy",[205,1347,394],{"class":233},[205,1349,1350],{"class":211}," whenChanged @adParams\n",[205,1352,1354,1357,1359,1361],{"class":207,"line":1353},67,[205,1355,1356],{"class":211},"$groupsByDN ",[205,1358,458],{"class":233},[205,1360,505],{"class":233},[205,1362,1215],{"class":211},[205,1364,1366,1369,1372,1375],{"class":207,"line":1365},68,[205,1367,1368],{"class":233},"foreach",[205,1370,1371],{"class":211}," ($group ",[205,1373,1374],{"class":233},"in",[205,1376,1377],{"class":211}," $allGroups) {\n",[205,1379,1381,1384,1386],{"class":207,"line":1380},69,[205,1382,1383],{"class":211},"    $groupsByDN[$group.DistinguishedName] ",[205,1385,458],{"class":233},[205,1387,1388],{"class":211}," $group\n",[205,1390,1392],{"class":207,"line":1391},70,[205,1393,709],{"class":211},[205,1395,1397],{"class":207,"line":1396},71,[205,1398,992],{"emptyLinePlaceholder":991},[205,1400,1402,1405,1407,1409],{"class":207,"line":1401},72,[205,1403,1404],{"class":211},"$depthCache ",[205,1406,458],{"class":233},[205,1408,505],{"class":233},[205,1410,1215],{"class":211},[205,1412,1414,1417,1419,1422,1424,1427],{"class":207,"line":1413},73,[205,1415,1416],{"class":211},"$circular ",[205,1418,458],{"class":233},[205,1420,1421],{"class":214}," New-Object",[205,1423,234],{"class":233},[205,1425,1426],{"class":211},"TypeName ",[205,1428,1429],{"class":240},"'System.Collections.Generic.HashSet[string]'\n",[205,1431,1433],{"class":207,"line":1432},74,[205,1434,992],{"emptyLinePlaceholder":991},[205,1436,1438,1441,1445],{"class":207,"line":1437},75,[205,1439,1440],{"class":233},"function",[205,1442,1444],{"class":1443},"svObZ"," Get-NestingDepth",[205,1446,1447],{"class":211}," {\n",[205,1449,1451,1454],{"class":207,"line":1450},76,[205,1452,1453],{"class":233},"    param",[205,1455,946],{"class":211},[205,1457,1459,1462,1464,1466,1468,1470,1473],{"class":207,"line":1458},77,[205,1460,1461],{"class":211},"        [",[205,1463,955],{"class":214},[205,1465,958],{"class":211},[205,1467,962],{"class":961},[205,1469,965],{"class":233},[205,1471,1472],{"class":214}," $true",[205,1474,971],{"class":211},[205,1476,1478,1480,1482,1485],{"class":207,"line":1477},78,[205,1479,1461],{"class":211},[205,1481,979],{"class":233},[205,1483,1484],{"class":211},"]$DistinguishedName",[205,1486,985],{"class":233},[205,1488,1490],{"class":207,"line":1489},79,[205,1491,992],{"emptyLinePlaceholder":991},[205,1493,1495,1497,1499,1501,1503,1505,1507],{"class":207,"line":1494},80,[205,1496,1461],{"class":211},[205,1498,955],{"class":214},[205,1500,958],{"class":211},[205,1502,962],{"class":961},[205,1504,965],{"class":233},[205,1506,1472],{"class":214},[205,1508,971],{"class":211},[205,1510,1512,1514,1517,1519,1521],{"class":207,"line":1511},81,[205,1513,1461],{"class":211},[205,1515,1516],{"class":233},"System.Collections.Generic.HashSet",[205,1518,622],{"class":211},[205,1520,979],{"class":233},[205,1522,1523],{"class":211},"]]$Path\n",[205,1525,1527],{"class":207,"line":1526},82,[205,1528,1529],{"class":211},"    )\n",[205,1531,1533],{"class":207,"line":1532},83,[205,1534,992],{"emptyLinePlaceholder":991},[205,1536,1538,1541],{"class":207,"line":1537},84,[205,1539,1540],{"class":233},"    if",[205,1542,1543],{"class":211}," ($depthCache.ContainsKey($DistinguishedName)) {\n",[205,1545,1547,1550],{"class":207,"line":1546},85,[205,1548,1549],{"class":233},"        return",[205,1551,1552],{"class":211}," $depthCache[$DistinguishedName]\n",[205,1554,1556],{"class":207,"line":1555},86,[205,1557,1558],{"class":211},"    }\n",[205,1560,1562],{"class":207,"line":1561},87,[205,1563,992],{"emptyLinePlaceholder":991},[205,1565,1567],{"class":207,"line":1566},88,[205,1568,1569],{"class":729},"    # Already on the current path: a circular nest. Record it and stop descending.\n",[205,1571,1573,1575,1577,1579],{"class":207,"line":1572},89,[205,1574,1540],{"class":233},[205,1576,489],{"class":211},[205,1578,1276],{"class":233},[205,1580,1581],{"class":211}," $Path.Add($DistinguishedName)) {\n",[205,1583,1585,1587,1590],{"class":207,"line":1584},90,[205,1586,1461],{"class":211},[205,1588,1589],{"class":233},"void",[205,1591,1592],{"class":211},"]$circular.Add($DistinguishedName)\n",[205,1594,1596,1598],{"class":207,"line":1595},91,[205,1597,1549],{"class":233},[205,1599,1600],{"class":214}," 0\n",[205,1602,1604],{"class":207,"line":1603},92,[205,1605,1558],{"class":211},[205,1607,1609],{"class":207,"line":1608},93,[205,1610,992],{"emptyLinePlaceholder":991},[205,1612,1614,1617,1619],{"class":207,"line":1613},94,[205,1615,1616],{"class":211},"    $maxDepth ",[205,1618,458],{"class":233},[205,1620,1600],{"class":214},[205,1622,1624,1627,1630,1632],{"class":207,"line":1623},95,[205,1625,1626],{"class":233},"    foreach",[205,1628,1629],{"class":211}," ($memberDN ",[205,1631,1374],{"class":233},[205,1633,1634],{"class":211}," $groupsByDN[$DistinguishedName].member) {\n",[205,1636,1638,1641],{"class":207,"line":1637},96,[205,1639,1640],{"class":233},"        if",[205,1642,1643],{"class":211}," ($groupsByDN.ContainsKey($memberDN)) {\n",[205,1645,1647,1650,1652,1655,1657,1659,1662,1664,1667,1669],{"class":207,"line":1646},97,[205,1648,1649],{"class":211},"            $childDepth ",[205,1651,458],{"class":233},[205,1653,1654],{"class":214}," 1",[205,1656,669],{"class":233},[205,1658,489],{"class":211},[205,1660,1661],{"class":214},"Get-NestingDepth",[205,1663,234],{"class":233},[205,1665,1666],{"class":211},"DistinguishedName $memberDN ",[205,1668,367],{"class":233},[205,1670,1671],{"class":211},"Path $Path)\n",[205,1673,1675,1678,1681,1683],{"class":207,"line":1674},98,[205,1676,1677],{"class":233},"            if",[205,1679,1680],{"class":211}," ($childDepth ",[205,1682,316],{"class":233},[205,1684,1685],{"class":211}," $maxDepth) {\n",[205,1687,1689,1692,1694],{"class":207,"line":1688},99,[205,1690,1691],{"class":211},"                $maxDepth ",[205,1693,458],{"class":233},[205,1695,1696],{"class":211}," $childDepth\n",[205,1698,1700],{"class":207,"line":1699},100,[205,1701,1702],{"class":211},"            }\n",[205,1704,1706],{"class":207,"line":1705},101,[205,1707,1708],{"class":211},"        }\n",[205,1710,1712],{"class":207,"line":1711},102,[205,1713,1558],{"class":211},[205,1715,1717],{"class":207,"line":1716},103,[205,1718,992],{"emptyLinePlaceholder":991},[205,1720,1722,1724,1726],{"class":207,"line":1721},104,[205,1723,952],{"class":211},[205,1725,1589],{"class":233},[205,1727,1728],{"class":211},"]$Path.Remove($DistinguishedName)\n",[205,1730,1732,1735,1737],{"class":207,"line":1731},105,[205,1733,1734],{"class":211},"    $depthCache[$DistinguishedName] ",[205,1736,458],{"class":233},[205,1738,1739],{"class":211}," $maxDepth\n",[205,1741,1743,1746],{"class":207,"line":1742},106,[205,1744,1745],{"class":233},"    return",[205,1747,1739],{"class":211},[205,1749,1751],{"class":207,"line":1750},107,[205,1752,709],{"class":211},[205,1754,1756],{"class":207,"line":1755},108,[205,1757,992],{"emptyLinePlaceholder":991},[205,1759,1761,1763,1766],{"class":207,"line":1760},109,[205,1762,1440],{"class":233},[205,1764,1765],{"class":1443}," Get-ValueCount",[205,1767,1447],{"class":211},[205,1769,1771,1773],{"class":207,"line":1770},110,[205,1772,1453],{"class":233},[205,1774,1775],{"class":211}," ($Value)\n",[205,1777,1779],{"class":207,"line":1778},111,[205,1780,992],{"emptyLinePlaceholder":991},[205,1782,1784],{"class":207,"line":1783},112,[205,1785,1786],{"class":729},"    # An unset attribute can come back as $null, and @($null).Count is 1, not 0.\n",[205,1788,1790,1792,1794,1797,1800],{"class":207,"line":1789},113,[205,1791,1540],{"class":233},[205,1793,489],{"class":211},[205,1795,1796],{"class":214},"$null",[205,1798,1799],{"class":233}," -eq",[205,1801,1802],{"class":211}," $Value) {\n",[205,1804,1806,1808],{"class":207,"line":1805},114,[205,1807,1549],{"class":233},[205,1809,1600],{"class":214},[205,1811,1813],{"class":207,"line":1812},115,[205,1814,1558],{"class":211},[205,1816,1818,1820,1822],{"class":207,"line":1817},116,[205,1819,1745],{"class":233},[205,1821,505],{"class":233},[205,1823,1824],{"class":211},"($Value).Count\n",[205,1826,1828],{"class":207,"line":1827},117,[205,1829,709],{"class":211},[205,1831,1833],{"class":207,"line":1832},118,[205,1834,992],{"emptyLinePlaceholder":991},[205,1836,1838,1840,1843],{"class":207,"line":1837},119,[205,1839,1440],{"class":233},[205,1841,1842],{"class":1443}," ConvertTo-LdapFilterValue",[205,1844,1447],{"class":211},[205,1846,1848,1850,1853,1855],{"class":207,"line":1847},120,[205,1849,1453],{"class":233},[205,1851,1852],{"class":211}," ([",[205,1854,979],{"class":233},[205,1856,1857],{"class":211},"]$Value)\n",[205,1859,1861],{"class":207,"line":1860},121,[205,1862,992],{"emptyLinePlaceholder":991},[205,1864,1866],{"class":207,"line":1865},122,[205,1867,1868],{"class":729},"    # RFC 4515 escaping; the backslash must be replaced first.\n",[205,1870,1872,1874,1877,1880,1882,1885,1888,1891,1893,1896,1898,1901,1903,1906,1908,1911,1913,1916,1919,1921,1924,1927,1929,1932,1934,1937],{"class":207,"line":1871},123,[205,1873,1745],{"class":233},[205,1875,1876],{"class":211}," $Value.Replace(",[205,1878,1879],{"class":240},"'\\'",[205,1881,394],{"class":233},[205,1883,1884],{"class":240}," '\\5c'",[205,1886,1887],{"class":211},").Replace(",[205,1889,1890],{"class":240},"'*'",[205,1892,394],{"class":233},[205,1894,1895],{"class":240}," '\\2a'",[205,1897,1887],{"class":211},[205,1899,1900],{"class":240},"'('",[205,1902,394],{"class":233},[205,1904,1905],{"class":240}," '\\28'",[205,1907,1887],{"class":211},[205,1909,1910],{"class":240},"')'",[205,1912,394],{"class":233},[205,1914,1915],{"class":240}," '\\29'",[205,1917,1918],{"class":211},").Replace([",[205,1920,979],{"class":233},[205,1922,1923],{"class":211},"][",[205,1925,1926],{"class":233},"char",[205,1928,307],{"class":211},[205,1930,1931],{"class":214},"0",[205,1933,394],{"class":233},[205,1935,1936],{"class":240}," '\\00'",[205,1938,1172],{"class":211},[205,1940,1942],{"class":207,"line":1941},124,[205,1943,709],{"class":211},[205,1945,1947],{"class":207,"line":1946},125,[205,1948,992],{"emptyLinePlaceholder":991},[205,1950,1952,1955,1957,1960,1962],{"class":207,"line":1951},126,[205,1953,1954],{"class":211},"$scopeSuffix ",[205,1956,458],{"class":233},[205,1958,1959],{"class":240}," ','",[205,1961,669],{"class":233},[205,1963,1964],{"class":211}," $SearchBase\n",[205,1966,1968,1971,1973,1975,1978,1980,1982],{"class":207,"line":1967},127,[205,1969,1970],{"class":211},"$reportGroups ",[205,1972,458],{"class":233},[205,1974,505],{"class":233},[205,1976,1977],{"class":211},"($allGroups ",[205,1979,511],{"class":233},[205,1981,514],{"class":214},[205,1983,1447],{"class":211},[205,1985,1987,1990,1992,1995,1998,2001,2003,2005,2008,2010,2012,2015,2018],{"class":207,"line":1986},128,[205,1988,1989],{"class":211},"    (",[205,1991,310],{"class":214},[205,1993,1994],{"class":211},".DistinguishedName ",[205,1996,1997],{"class":233},"-eq",[205,1999,2000],{"class":211}," $SearchBase ",[205,2002,583],{"class":233},[205,2004,586],{"class":214},[205,2006,2007],{"class":211},".DistinguishedName.EndsWith($scopeSuffix",[205,2009,394],{"class":233},[205,2011,325],{"class":211},[205,2013,2014],{"class":233},"System.StringComparison",[205,2016,2017],{"class":211},"]::OrdinalIgnoreCase)) ",[205,2019,2020],{"class":233},"-and\n",[205,2022,2024,2027,2029,2031,2034,2036,2039],{"class":207,"line":2023},129,[205,2025,2026],{"class":211},"    ($IncludeDistributionGroups ",[205,2028,583],{"class":233},[205,2030,586],{"class":214},[205,2032,2033],{"class":211},".GroupCategory ",[205,2035,1997],{"class":233},[205,2037,2038],{"class":240}," 'Security'",[205,2040,1172],{"class":211},[205,2042,2044],{"class":207,"line":2043},130,[205,2045,2046],{"class":211},"})\n",[205,2048,2050],{"class":207,"line":2049},131,[205,2051,992],{"emptyLinePlaceholder":991},[205,2053,2055,2058,2060,2063,2066,2069,2071,2074,2076,2079,2081],{"class":207,"line":2054},132,[205,2056,2057],{"class":214},"Write-Host",[205,2059,489],{"class":211},[205,2061,2062],{"class":240},"\"Loaded {0:N0} groups from {1}. Reporting {2:N0} group(s) under {3}.\"",[205,2064,2065],{"class":233}," -f",[205,2067,2068],{"class":211}," $allGroups.Count",[205,2070,394],{"class":233},[205,2072,2073],{"class":211}," $domainDN",[205,2075,394],{"class":233},[205,2077,2078],{"class":211}," $reportGroups.Count",[205,2080,394],{"class":233},[205,2082,2083],{"class":211}," $SearchBase)\n",[205,2085,2087],{"class":207,"line":2086},133,[205,2088,992],{"emptyLinePlaceholder":991},[205,2090,2092,2095,2097],{"class":207,"line":2091},134,[205,2093,2094],{"class":211},"$now ",[205,2096,458],{"class":233},[205,2098,2099],{"class":214}," Get-Date\n",[205,2101,2103,2106,2108,2111,2113],{"class":207,"line":2102},135,[205,2104,2105],{"class":211},"$cutoffDate ",[205,2107,458],{"class":233},[205,2109,2110],{"class":211}," $now.AddDays(",[205,2112,367],{"class":233},[205,2114,2115],{"class":211},"$StaleDays)\n",[205,2117,2119,2122,2124],{"class":207,"line":2118},136,[205,2120,2121],{"class":211},"$index ",[205,2123,458],{"class":233},[205,2125,1600],{"class":214},[205,2127,2129],{"class":207,"line":2128},137,[205,2130,992],{"emptyLinePlaceholder":991},[205,2132,2134,2137,2139,2142,2144,2146],{"class":207,"line":2133},138,[205,2135,2136],{"class":211},"$report ",[205,2138,458],{"class":233},[205,2140,2141],{"class":233}," foreach",[205,2143,1371],{"class":211},[205,2145,1374],{"class":233},[205,2147,2148],{"class":211}," $reportGroups) {\n",[205,2150,2152,2155],{"class":207,"line":2151},139,[205,2153,2154],{"class":211},"    $index",[205,2156,2157],{"class":233},"++\n",[205,2159,2161,2164,2166,2169,2172,2174,2177,2179,2182,2185,2188,2190,2193],{"class":207,"line":2160},140,[205,2162,2163],{"class":214},"    Write-Progress",[205,2165,234],{"class":233},[205,2167,2168],{"class":211},"Activity ",[205,2170,2171],{"class":240},"'Analysing groups'",[205,2173,234],{"class":233},[205,2175,2176],{"class":211},"Status $group.Name ",[205,2178,367],{"class":233},[205,2180,2181],{"class":211},"PercentComplete (($index ",[205,2183,2184],{"class":233},"\u002F",[205,2186,2187],{"class":211}," $reportGroups.Count) ",[205,2189,1325],{"class":233},[205,2191,2192],{"class":214}," 100",[205,2194,1172],{"class":211},[205,2196,2198],{"class":207,"line":2197},141,[205,2199,992],{"emptyLinePlaceholder":991},[205,2201,2203,2206,2208,2210,2212,2215,2217,2220,2223,2225,2227,2230],{"class":207,"line":2202},142,[205,2204,2205],{"class":211},"    $depth ",[205,2207,458],{"class":233},[205,2209,1444],{"class":214},[205,2211,234],{"class":233},[205,2213,2214],{"class":211},"DistinguishedName $group.DistinguishedName ",[205,2216,367],{"class":233},[205,2218,2219],{"class":211},"Path (",[205,2221,2222],{"class":214},"New-Object",[205,2224,234],{"class":233},[205,2226,1426],{"class":211},[205,2228,2229],{"class":240},"'System.Collections.Generic.HashSet[string]'",[205,2231,1172],{"class":211},[205,2233,2235,2238,2240,2242,2245,2247,2249,2251,2253,2255,2258,2260],{"class":207,"line":2234},143,[205,2236,2237],{"class":211},"    $childGroups ",[205,2239,458],{"class":233},[205,2241,505],{"class":233},[205,2243,2244],{"class":211},"($group.member ",[205,2246,511],{"class":233},[205,2248,514],{"class":214},[205,2250,517],{"class":211},[205,2252,310],{"class":214},[205,2254,322],{"class":233},[205,2256,2257],{"class":211}," $groupsByDN.ContainsKey(",[205,2259,310],{"class":214},[205,2261,594],{"class":211},[205,2263,2265,2268,2270,2272,2274],{"class":207,"line":2264},144,[205,2266,2267],{"class":211},"    $directMembers ",[205,2269,458],{"class":233},[205,2271,1765],{"class":214},[205,2273,234],{"class":233},[205,2275,2276],{"class":211},"Value $group.member\n",[205,2278,2280],{"class":207,"line":2279},145,[205,2281,992],{"emptyLinePlaceholder":991},[205,2283,2285],{"class":207,"line":2284},146,[205,2286,2287],{"class":729},"    # Every non-group object that is a member at any depth, counted on the DC in one query.\n",[205,2289,2291,2294,2296,2299,2301,2303,2306,2308],{"class":207,"line":2290},147,[205,2292,2293],{"class":211},"    $chainFilter ",[205,2295,458],{"class":233},[205,2297,2298],{"class":240}," '(&(!(objectClass=group))(memberOf:1.2.840.113556.1.4.1941:={0}))'",[205,2300,2065],{"class":233},[205,2302,489],{"class":211},[205,2304,2305],{"class":214},"ConvertTo-LdapFilterValue",[205,2307,234],{"class":233},[205,2309,2310],{"class":211},"Value $group.DistinguishedName)\n",[205,2312,2314,2317,2319,2321,2323,2326,2328,2331,2333],{"class":207,"line":2313},148,[205,2315,2316],{"class":211},"    $transitive ",[205,2318,458],{"class":233},[205,2320,505],{"class":233},[205,2322,958],{"class":211},[205,2324,2325],{"class":214},"Get-ADObject",[205,2327,234],{"class":233},[205,2329,2330],{"class":211},"LDAPFilter $chainFilter ",[205,2332,367],{"class":233},[205,2334,2335],{"class":211},"SearchBase $domainDN @adParams).Count\n",[205,2337,2339],{"class":207,"line":2338},149,[205,2340,992],{"emptyLinePlaceholder":991},[205,2342,2344,2347,2349,2351,2353,2356,2358],{"class":207,"line":2343},150,[205,2345,2346],{"class":211},"    $flags ",[205,2348,458],{"class":233},[205,2350,1421],{"class":214},[205,2352,234],{"class":233},[205,2354,2355],{"class":211},"TypeName System.Collections.Generic.List[",[205,2357,979],{"class":233},[205,2359,2360],{"class":211},"]\n",[205,2362,2364,2366,2369,2372,2375,2378],{"class":207,"line":2363},151,[205,2365,1540],{"class":233},[205,2367,2368],{"class":211}," ($group.whenChanged ",[205,2370,2371],{"class":233},"-lt",[205,2373,2374],{"class":211}," $cutoffDate) { $flags.Add(",[205,2376,2377],{"class":240},"'Stale'",[205,2379,2380],{"class":211},") }\n",[205,2382,2384,2386,2389,2391,2393,2396,2399],{"class":207,"line":2383},152,[205,2385,1540],{"class":233},[205,2387,2388],{"class":211}," ($transitive ",[205,2390,1997],{"class":233},[205,2392,319],{"class":214},[205,2394,2395],{"class":211},") { $flags.Add(",[205,2397,2398],{"class":240},"'Empty'",[205,2400,2380],{"class":211},[205,2402,2404,2406,2409,2412,2415,2418],{"class":207,"line":2403},153,[205,2405,1540],{"class":233},[205,2407,2408],{"class":211}," ($depth ",[205,2410,2411],{"class":233},"-ge",[205,2413,2414],{"class":211}," $MaxNestingDepth) { $flags.Add(",[205,2416,2417],{"class":240},"'DeeplyNested'",[205,2419,2380],{"class":211},[205,2421,2423,2425,2427,2429,2432,2435],{"class":207,"line":2422},154,[205,2424,1540],{"class":233},[205,2426,489],{"class":211},[205,2428,1276],{"class":233},[205,2430,2431],{"class":211}," $group.managedBy) { $flags.Add(",[205,2433,2434],{"class":240},"'NoManagedBy'",[205,2436,2380],{"class":211},[205,2438,2440,2442,2445,2448],{"class":207,"line":2439},155,[205,2441,1540],{"class":233},[205,2443,2444],{"class":211}," ($circular.Contains($group.DistinguishedName)) { $flags.Add(",[205,2446,2447],{"class":240},"'Circular'",[205,2449,2380],{"class":211},[205,2451,2453],{"class":207,"line":2452},156,[205,2454,992],{"emptyLinePlaceholder":991},[205,2456,2458,2460,2462,2464,2466],{"class":207,"line":2457},157,[205,2459,952],{"class":211},[205,2461,625],{"class":233},[205,2463,307],{"class":211},[205,2465,630],{"class":233},[205,2467,633],{"class":211},[205,2469,2471,2474,2476],{"class":207,"line":2470},158,[205,2472,2473],{"class":211},"        Name              ",[205,2475,458],{"class":233},[205,2477,2478],{"class":211}," $group.Name\n",[205,2480,2482,2485,2487],{"class":207,"line":2481},159,[205,2483,2484],{"class":211},"        Scope             ",[205,2486,458],{"class":233},[205,2488,2489],{"class":211}," $group.GroupScope\n",[205,2491,2493,2496,2498],{"class":207,"line":2492},160,[205,2494,2495],{"class":211},"        Category          ",[205,2497,458],{"class":233},[205,2499,2500],{"class":211}," $group.GroupCategory\n",[205,2502,2504,2507,2509],{"class":207,"line":2503},161,[205,2505,2506],{"class":211},"        NestingDepth      ",[205,2508,458],{"class":233},[205,2510,2511],{"class":211}," $depth\n",[205,2513,2515,2518,2520],{"class":207,"line":2514},162,[205,2516,2517],{"class":211},"        ChildGroups       ",[205,2519,458],{"class":233},[205,2521,2522],{"class":211}," $childGroups\n",[205,2524,2526,2529,2531],{"class":207,"line":2525},163,[205,2527,2528],{"class":211},"        DirectMembers     ",[205,2530,458],{"class":233},[205,2532,2533],{"class":211}," $directMembers\n",[205,2535,2537,2540,2542],{"class":207,"line":2536},164,[205,2538,2539],{"class":211},"        TransitiveMembers ",[205,2541,458],{"class":233},[205,2543,2544],{"class":211}," $transitive\n",[205,2546,2548,2551,2553,2555,2557],{"class":207,"line":2547},165,[205,2549,2550],{"class":211},"        NestedInCount     ",[205,2552,458],{"class":233},[205,2554,1765],{"class":214},[205,2556,234],{"class":233},[205,2558,2559],{"class":211},"Value $group.memberOf\n",[205,2561,2563,2566,2568,2570,2572,2575,2577],{"class":207,"line":2562},166,[205,2564,2565],{"class":211},"        DaysSinceChanged  ",[205,2567,458],{"class":233},[205,2569,325],{"class":211},[205,2571,304],{"class":233},[205,2573,2574],{"class":211},"]($now ",[205,2576,367],{"class":233},[205,2578,2579],{"class":211}," $group.whenChanged).TotalDays\n",[205,2581,2583,2586,2588,2590,2593],{"class":207,"line":2582},167,[205,2584,2585],{"class":211},"        HasManagedBy      ",[205,2587,458],{"class":233},[205,2589,325],{"class":211},[205,2591,2592],{"class":233},"bool",[205,2594,2595],{"class":211},"]$group.managedBy\n",[205,2597,2599,2602,2604,2607,2610],{"class":207,"line":2598},168,[205,2600,2601],{"class":211},"        Flags             ",[205,2603,458],{"class":233},[205,2605,2606],{"class":211}," $flags ",[205,2608,2609],{"class":233},"-join",[205,2611,2612],{"class":240}," ','\n",[205,2614,2616,2619,2621],{"class":207,"line":2615},169,[205,2617,2618],{"class":211},"        DistinguishedName ",[205,2620,458],{"class":233},[205,2622,2623],{"class":211}," $group.DistinguishedName\n",[205,2625,2627],{"class":207,"line":2626},170,[205,2628,1558],{"class":211},[205,2630,2632],{"class":207,"line":2631},171,[205,2633,709],{"class":211},[205,2635,2637],{"class":207,"line":2636},172,[205,2638,992],{"emptyLinePlaceholder":991},[205,2640,2642,2645,2647,2649,2651,2653],{"class":207,"line":2641},173,[205,2643,2644],{"class":214},"Write-Progress",[205,2646,234],{"class":233},[205,2648,2168],{"class":211},[205,2650,2171],{"class":240},[205,2652,234],{"class":233},[205,2654,2655],{"class":211},"Completed\n",[205,2657,2659],{"class":207,"line":2658},174,[205,2660,992],{"emptyLinePlaceholder":991},[205,2662,2664,2667,2669,2672,2674,2677,2679,2682,2684,2687,2689,2692,2695,2697,2699,2702,2704,2706,2708,2710,2713,2715,2717,2719],{"class":207,"line":2663},175,[205,2665,2666],{"class":211},"$sortedReport ",[205,2668,458],{"class":233},[205,2670,2671],{"class":211}," $report ",[205,2673,511],{"class":233},[205,2675,2676],{"class":214}," Sort-Object",[205,2678,234],{"class":233},[205,2680,2681],{"class":211},"Property ",[205,2683,630],{"class":233},[205,2685,2686],{"class":211},"{ Expression ",[205,2688,458],{"class":233},[205,2690,2691],{"class":240}," 'NestingDepth'",[205,2693,2694],{"class":211},"; Descending ",[205,2696,458],{"class":233},[205,2698,1472],{"class":214},[205,2700,2701],{"class":211}," }",[205,2703,394],{"class":233},[205,2705,505],{"class":233},[205,2707,2686],{"class":211},[205,2709,458],{"class":233},[205,2711,2712],{"class":240}," 'TransitiveMembers'",[205,2714,2694],{"class":211},[205,2716,458],{"class":233},[205,2718,1472],{"class":214},[205,2720,2721],{"class":211}," }\n",[205,2723,2725],{"class":207,"line":2724},176,[205,2726,992],{"emptyLinePlaceholder":991},[205,2728,2730,2732],{"class":207,"line":2729},177,[205,2731,2666],{"class":211},[205,2733,344],{"class":233},[205,2735,2737,2739,2741,2743,2746,2748,2750,2752,2755,2757,2759,2761,2763,2765,2768,2770,2773,2775,2778,2780,2783,2785,2788],{"class":207,"line":2736},178,[205,2738,378],{"class":214},[205,2740,234],{"class":233},[205,2742,383],{"class":211},[205,2744,2745],{"class":214},"50",[205,2747,234],{"class":233},[205,2749,391],{"class":211},[205,2751,394],{"class":233},[205,2753,2754],{"class":211}," Scope",[205,2756,394],{"class":233},[205,2758,407],{"class":211},[205,2760,394],{"class":233},[205,2762,402],{"class":211},[205,2764,394],{"class":233},[205,2766,2767],{"class":211}," DirectMembers",[205,2769,394],{"class":233},[205,2771,2772],{"class":211}," TransitiveMembers",[205,2774,394],{"class":233},[205,2776,2777],{"class":211}," DaysSinceChanged",[205,2779,394],{"class":233},[205,2781,2782],{"class":211}," HasManagedBy",[205,2784,394],{"class":233},[205,2786,2787],{"class":211}," Flags ",[205,2789,344],{"class":233},[205,2791,2793,2796,2798],{"class":207,"line":2792},179,[205,2794,2795],{"class":214},"    Format-Table",[205,2797,234],{"class":233},[205,2799,2800],{"class":211},"AutoSize\n",[205,2802,2804],{"class":207,"line":2803},180,[205,2805,992],{"emptyLinePlaceholder":991},[205,2807,2809,2811],{"class":207,"line":2808},181,[205,2810,1221],{"class":233},[205,2812,2813],{"class":211}," ($OutputPath) {\n",[205,2815,2817,2820,2822,2825,2827,2830,2832,2835,2837],{"class":207,"line":2816},182,[205,2818,2819],{"class":211},"    $sortedReport ",[205,2821,511],{"class":233},[205,2823,2824],{"class":214}," Export-Csv",[205,2826,234],{"class":233},[205,2828,2829],{"class":211},"Path $OutputPath ",[205,2831,367],{"class":233},[205,2833,2834],{"class":211},"NoTypeInformation ",[205,2836,367],{"class":233},[205,2838,2839],{"class":211},"Encoding UTF8\n",[205,2841,2843,2846,2849,2852,2854,2857,2860,2863,2866],{"class":207,"line":2842},183,[205,2844,2845],{"class":214},"    Write-Host",[205,2847,2848],{"class":240}," \"Full report (",[205,2850,2851],{"class":233},"$",[205,2853,958],{"class":240},[205,2855,2856],{"class":211},"$report.Count",[205,2858,2859],{"class":240},")",[205,2861,2862],{"class":240}," rows) exported to ",[205,2864,2865],{"class":211},"$OutputPath",[205,2867,2868],{"class":240},"\"\n",[205,2870,2872],{"class":207,"line":2871},184,[205,2873,709],{"class":211},[28,2875,2877],{"id":2876},"notes","Notes",[33,2879,2880,2901,2914,2923,2935,2942,2949,2952],{},[36,2881,2882,2889,2890,2893,2894,489,2897,2900],{},[18,2883,2884,2885,2888],{},"Why not ",[23,2886,2887],{},"Get-ADGroupMember -Recursive","?"," The first draft of this script used it per group, plus a recursive depth walk that called ",[23,2891,2892],{},"Get-ADGroupMember"," again for every child group, so a domain with thousands of groups turned into tens of thousands of Web Services round trips, many of them expanding the same subtrees over and over. Loading the groups once and asking the DC for transitive members with ",[23,2895,2896],{},"LDAP_MATCHING_RULE_IN_CHAIN",[23,2898,2899],{},"1.2.840.113556.1.4.1941",") replaces all of that with one bulk read plus one paged query per reported group. It also means nested groups are never counted as members, only the accounts they reach. Microsoft documents that rule as walking \"the chain of ancestry in objects all the way to the root\", and warns that high fan-out chain queries are processor-intensive for the DC, so run the report off-hours on a very large domain.",[36,2902,2903,2906,2907,2910,2911,2913],{},[18,2904,2905],{},"Transitive member counts miss two things."," A user's primary group is recorded on the user as a RID in ",[23,2908,2909],{},"primaryGroupID"," (Domain Users by default), not as a value in the group's ",[23,2912,44],{}," attribute, so Domain Users and Domain Computers will look nearly empty. And the chain query only searches this domain's naming context: members from other domains in the same forest aren't counted, while members from external or forest trusts are, because they're stored here as foreign security principals. Treat those as known exceptions when reading the numbers.",[36,2915,2916,2918,2919,2922],{},[23,2917,55],{}," is not replicated: each DC keeps its own value, and it moves whenever any attribute on the group changes, not only membership. It's a coarse staleness signal. If two runs disagree, pin the queries to one DC with the script's ",[23,2920,2921],{},"-Server"," parameter.",[36,2924,2925,2928,2929,2931,2932,2934],{},[23,2926,2927],{},"NoManagedBy"," checks ",[23,2930,51],{},", which is the only field AD has natively for a group's accountable owner. (The object's security descriptor also records an owner, but that's the account that created it, usually an admin or Domain Admins, not someone who answers for the membership.) An empty ",[23,2933,51],{}," doesn't mean nobody owns the group, but it does mean nobody can be asked from the directory alone, and that is the question an access review keeps running into.",[36,2936,2937,2938,2941],{},"The depth cache makes the walk linear in the number of groups. In a circular nest the cached depth for the groups inside the loop is a lower bound, not exact, which is fine: a ",[23,2939,2940],{},"Circular"," flag already means someone needs to look at it.",[36,2943,2944,2945,2948],{},"The ",[23,2946,2947],{},"Empty"," flag catches abandoned groups but is not itself a reason to delete one. Some groups are intentionally empty scaffolding for a process that has not started yet, and some grant rights through ACLs or GPO security filtering that are still referenced. Treat the report as a worklist for a human, not an automatic prune.",[36,2950,2951],{},"I run this quarterly rather than continuously. Group sprawl is a slow-moving problem, and the CSV is more useful compared release over release (is maximum nesting getting worse, are the same stale groups still stale) than as a real-time dashboard.",[36,2953,2954,2955,2957,2958,2960],{},"If the token estimate for a user comes out near the default ",[23,2956,25],{},", Microsoft's documented resolution is raising ",[23,2959,25],{}," in the registry on every computer involved in the authentication. The same article explains why that has a ceiling: IIS uses a 64 KB request buffer and the ticket is Base64-encoded in HTTP (133 percent of its size), which is where the 48,000-byte default comes from, and values above 65,535 break other protocols. In my experience, reducing the group count by flattening nests is the fix that keeps working. The estimate is also a rough one: it ignores resource SID compression, claims and delegation, which the article covers.",[28,2962,2964],{"id":2963},"source","Source",[33,2966,2967,2976,2983,2990,2997],{},[36,2968,2969],{},[2970,2971,2975],"a",{"href":2972,"rel":2973},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Ftroubleshoot\u002Fwindows-server\u002Fwindows-security\u002Fkerberos-authentication-problems-if-user-belongs-to-groups",[2974],"nofollow","Kerberos authentication problems if a user belongs to many groups (MaxTokenSize, token size formula)",[36,2977,2978],{},[2970,2979,2982],{"href":2980,"rel":2981},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fadsi\u002Fsearch-filter-syntax",[2974],"Search filter syntax (LDAP_MATCHING_RULE_IN_CHAIN)",[36,2984,2985],{},[2970,2986,2989],{"href":2987,"rel":2988},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fadschema\u002Fa-whenchanged",[2974],"When-Changed attribute",[36,2991,2992],{},[2970,2993,2996],{"href":2994,"rel":2995},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fadschema\u002Fa-tokengroups",[2974],"Token-Groups attribute",[36,2998,2999],{},[2970,3000,3003],{"href":3001,"rel":3002},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fadschema\u002Fa-primarygroupid",[2974],"Primary-Group-ID attribute",[3005,3006,3007],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":201,"searchDepth":295,"depth":295,"links":3009},[3010,3011,3012,3013,3014,3015],{"id":30,"depth":295,"text":31},{"id":62,"depth":295,"text":63},{"id":190,"depth":295,"text":191},{"id":718,"depth":295,"text":719},{"id":2876,"depth":295,"text":2877},{"id":2963,"depth":295,"text":2964},"techcolumnist",[3018,3019],"ps","scripts","2025-12-03T14:00:00Z","Rank AD security groups by nesting depth, transitive member count, ownership and staleness for an access review, plus a per-user Kerberos token size estimate.","md",false,null,{},"\u002Fblog\u002F2025\u002F12\u002F03\u002Fpowershell-active-directory-report-group-membership-sprawl",{"title":6,"description":3021},[3016],"blog\u002F2025\u002F12\u002F03\u002Fpowershell-active-directory-report-group-membership-sprawl",[200,3031,3032],"active-directory","reporting","\u002F2025\u002F12\u002F03\u002Fpowershell-active-directory-report-group-membership-sprawl\u002F","cs5NiilJyxjT6gpAfazxVeZqL_J7sOYw-ceOYkiteyo",{"title":3036,"description":3037,"date":3038,"url":3039,"categories":3040,"tags":3042,"image":3024,"readingTime":347,"canonical":3016,"sites":3046,"series":3024,"seriesOrder":3024},"Python: Discovery – Inventory VMware Hosts and VMs via pyVmomi","A pyVmomi script that pulls ESXi host and VM inventory from vCenter with one PropertyCollector query per type and writes two CSVs for a discovery pass.","2025-12-10T14:00:00Z","\u002F2025\u002F12\u002F10\u002Fpython-discovery-inventory-vmware-hosts-and-vms-via-pyvmomi\u002F",[3019,3041],"engineering",[3043,3044,3045],"python","vmware","discovery",[3016],{"title":3048,"description":3049,"date":3050,"url":3051,"categories":3052,"tags":3053,"image":3024,"readingTime":375,"canonical":3016,"sites":3055,"series":3024,"seriesOrder":3024},"PowerShell: Windows Server – Disable SMBv1 Fleet-Wide and Verify","A script that audits SMBv1 access (event 3000), disables the SMBv1 server and optional feature across Windows servers, and reports which ones still need a restart.","2025-11-26T14:00:00Z","\u002F2025\u002F11\u002F26\u002Fpowershell-windows-server-disable-smbv1-fleet-wide-and-verify-it-stuck\u002F",[3018,3019],[200,3054],"windows",[3016],[3057,3065,3073],{"title":3058,"description":3059,"date":3060,"url":3061,"categories":3062,"tags":3063,"image":3024,"readingTime":547,"canonical":3016,"sites":3064,"series":3024,"seriesOrder":3024},"PowerShell: Windows – Discover Local Admins Across Every Workstation","A script that reads the local Administrators group on every workstation by SID, survives orphaned-SID errors, and flags members outside an approved allowlist.","2025-10-08T14:00:00Z","\u002F2025\u002F10\u002F08\u002Fpowershell-windows-discover-local-admins-across-every-workstation\u002F",[3018,3019],[200,3054,3045,3031],[3016],{"title":3066,"description":3067,"date":3068,"url":3069,"categories":3070,"tags":3071,"image":3024,"readingTime":547,"canonical":3016,"sites":3072,"series":3024,"seriesOrder":3024},"PowerShell: Active Directory – Bulk Password Reset with Forced Change","Reset a list of AD accounts to random, policy-length passwords, force a change at next logon, handle never-expires and lockouts, and write a report.","2025-10-01T14:00:00Z","\u002F2025\u002F10\u002F01\u002Fpowershell-active-directory-bulk-reset-passwords-with-a-forced-change\u002F",[3018,3019],[200,3031],[3016],{"title":3074,"description":3075,"date":3076,"url":3077,"categories":3078,"tags":3079,"image":3024,"readingTime":547,"canonical":3016,"sites":3081,"series":3024,"seriesOrder":3024},"PowerShell: Exchange Online – Report Mailboxes Approaching Their Quota","Flag Exchange Online mailboxes above a percentage of their ProhibitSendQuota, plus Recoverable Items pressure, using the EXO V3 Get-EXO cmdlets.","2025-09-03T14:00:00Z","\u002F2025\u002F09\u002F03\u002Fpowershell-exchange-online-report-mailboxes-approaching-their-quota\u002F",[3018,3019],[200,3080,3032],"exchange-online",[3016],{"doc":3024,"posts":3083},[],1790052513232]