In a Windows domain environment, how could I count the top N users
with more groupmembership?
I'd like to know both the indirect and direct groups.
Nested AD Group Membership Counts
The AD enabled user account group membership nested methods can take quite some time so be patient if your AD is large (or not perhaps too). The Powershell logic to get the non-nested AD group membership for all enabled user accounts is below the nested logic I provided if you want to run those.
Get-NestedAdGroupMembership Function Source
Top 10 Highest Count Records Output to Console
function Get-NestedAdGroupMembership {
Param(
[parameter(Mandatory=$true)]
[alias("account", "username")]
$user,
[parameter(Mandatory=$false)]
$grouphash = @{}
)
$groups = @(Get-ADPrincipalGroupMembership -Identity $user | select -ExpandProperty distinguishedname)
foreach ($group in $groups) {
if ( $grouphash[$group] -eq $null) {
$grouphash[$group] = $true
$group
Get-NestedAdGroupMembership $group $grouphash
}
}
}
$TempDir = $env:Temp
$TempPSFile = "$TempDir\~PSTempADMembershipCount.lst"
$Users = Get-ADUser -Filter {Enabled -eq $True} | Select-Object sAMAccountName
If (Test-Path $TempPSFile){
Remove-Item $TempPSFile
}
ForEach ($User in $Users){
$SamName = $User.sAMAccountName
$Count = Get-NestedAdGroupMembership $User.sAMAccountName | Measure-Object | Select-Object -Expand Count
"$SamName,$count" | Out-File -Filepath $TempPSFile -Append
}
Import-Csv -Header Username,GroupCount -Path $TempPSFile | Sort-Object {[int] $_.GroupCount} -Descending | Select-Object -First 10
All Records Output to Console Descending Order
function Get-NestedAdGroupMembership {
Param(
[parameter(Mandatory=$true)]
[alias("account", "username")]
$user,
[parameter(Mandatory=$false)]
$grouphash = @{}
)
$groups = @(Get-ADPrincipalGroupMembership -Identity $user | select -ExpandProperty distinguishedname)
foreach ($group in $groups) {
if ( $grouphash[$group] -eq $null) {
$grouphash[$group] = $true
$group
Get-NestedAdGroupMembership $group $grouphash
}
}
}
$TempDir = $env:Temp
$TempPSFile = "$TempDir\~PSTempADMembershipCount.lst"
$Users = Get-ADUser -Filter {Enabled -eq $True} | Select-Object sAMAccountName
If (Test-Path $TempPSFile){
Remove-Item $TempPSFile
}
ForEach ($User in $Users){
$SamName = $User.sAMAccountName
$Count = Get-NestedAdGroupMembership $User.sAMAccountName | Measure-Object | Select-Object -Expand Count
"$SamName,$count" | Out-File -Filepath $TempPSFile -Append
}
Import-Csv -Header Username,GroupCount -Path $TempPSFile | Sort-Object {[int] $_.GroupCount} -Descending | Select-Object
Non-Nested AD Group Membership Counts
Top 10 Highest Count Records Output to Console
$TempDir = $env:Temp
$TempPSFile = "$TempDir\~PSTempADMembershipCount.lst"
$Users = Get-ADUser -Filter {Enabled -eq $True} | Select-Object sAMAccountName
If (Test-Path $TempPSFile){
Remove-Item $TempPSFile
}
ForEach ($User in $Users){
$SamName = $User.sAMAccountName
$Count = Get-ADPrincipalGroupMembership $User.sAMAccountName | Measure-Object | Select-Object -Expand Count
"$SamName,$count" | Out-File -Filepath $TempPSFile -Append
}
Import-Csv -Header Username,GroupCount -Path $TempPSFile | Sort-Object {[int] $_.GroupCount} -Descending | Select-Object -First 10
All Records Output to Console Descending Order
$TempDir = $env:Temp
$TempPSFile = "$TempDir\~PSTempADMembershipCount.lst"
$Users = Get-ADUser -Filter {Enabled -eq $True} | Select-Object sAMAccountName
If (Test-Path $TempPSFile){
Remove-Item $TempPSFile
}
ForEach ($User in $Users){
$SamName = $User.sAMAccountName
$Count = Get-ADPrincipalGroupMembership $User.sAMAccountName | Measure-Object | Select-Object -Expand Count
"$SamName,$count" | Out-File -Filepath $TempPSFile -Append
}
Import-Csv -Header Username,GroupCount -Path $TempPSFile | Sort-Object {[int] $_.GroupCount} -Descending
Notes
In all above logic examples, I have the syntax of Get-ADUser -Filter {Enabled -eq $True}
in the Get-ADUser
command so this filters to ensure you're only querying ENABLED AD user account object. This means DISABLED accounts will NOT be included in the results unless this filter is removed or changed.
The above examples have ALL been tested and confirmed to work as expected in my case, so these have all been verified to work successfully.
Potential Issues
If you run into an issue with an error message of The server was unable to process the request due to an internal error in Powershell when running these processes above, then you may want to take a look at this article for a potential solution that worked in my case (click the provided link).
Further Reading and Resources