This is an adapted version of my answer here to omit groups without any user object.
Unfortunately, using Get-ADGroupMember
together with switch -Recursive
will not return members that are groups.
As the docs state:
If the Recursive parameter is specified, the cmdlet gets all members
in the hierarchy of the group that do not contain child objects.
To get an array of nested group objects within a certain parent group, you will need a recursive function like below:
function Get-NestedADGroup {
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[Alias ('Identity')]
[string]$Group,
# the other parameters are optional
[string]$Server = $null,
[string]$SearchBase = $null,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[string]$SearchScope = 'Subtree'
)
$params = @{
Identity = $Group
SearchScope = $SearchScope
Properties = 'Members'
ErrorAction = 'SilentlyContinue'
}
if (![string]::IsNullOrWhiteSpace($Server)) { $params['Server'] = $Server }
if (![string]::IsNullOrWhiteSpace($SearchBase)) { $params['SearchBase'] = $SearchBase }
$adGroup = Get-ADGroup @params
if ($adGroup) {
if (-not $script:groupsHash.ContainsKey($Group)) {
# output this group object only if it has at least one user object
if (@($adGroup.Members | Where-Object {$_.objectClass -eq 'user'}).Count -gt 0) {
$adGroup
}
# avoid circular group references
$script:groupsHash[$Group] = $true
# and recurse to get the nested groups
foreach ($group in ($adGroup.Members | Where-Object {$_.objectClass -eq 'group'})) {
Get-NestedADGroup -Group $group.DistinguishedName -Server $Server -SearchBase $SearchBase
}
}
}
else {
Write-Warning "Group '$($Group)' could not be found.."
}
}
# create a Hashtable to avoid circular nested groups
$groupsHash = @{}
# call the function
$result = Get-NestedADGroup -Group 'SpecificGroup'
# output just the names if you like
$result.Name
# save to CSV
$result | Export-Csv -Path 'X:\Somewhere\SubgroupsWithAtLeastOneUser.csv' -NoTypeInformation