find member,memberOf for Groups only

I’m trying to pull a report that:

only lists Security Groups that have other Security Groups as members and/or are memberOf other security groups.

I have this code so far:

Get-ADGroup -Filter {GroupCategory -eq 'security'} -SearchBase 'DC=child,DC=root,DC=com' -Credential $creds -Properties * | select member,memberOf

I want to then pull the DN, SamAccountName for those results but haven’t gotten close to narrowing down the initial results.

thanks

not fully tested, but you might try something like this

$notsecuritygroup = New-Object System.Collections.ArrayList

Write-Host 'getting all security groups...'
$groups = Get-ADGroup -Filter "groupcategory -eq 'security'" -properties member | select name, member

# all groups progress
$index = 0
$total = $groups.Count
$starttime = $lasttime = Get-Date

# all members progress
$index2 = 0
$total2 = $groups.member.Count
$starttime2 = $lasttime2 = Get-Date

$results = foreach ($group in $groups) {
    # all groups progress
    $index++
    $currtime = (Get-Date) - $starttime
    $avg = $currtime.TotalSeconds / $index
    $last = ((Get-Date) - $lasttime).TotalSeconds
    $left = $total - $index
    $WrPrgParam = @{
        Activity = (
            "ALL GROUPS $(Get-Date -f s)",
            "Total: $($currtime -replace '\..*')",
            "Avg: $('{0:N2}' -f $avg)",
            "Last: $('{0:N2}' -f $last)",
            "ETA: $('{0:N2}' -f ($avg * $left / 60))",
            "min ($([string](Get-Date).AddSeconds($avg*$left) -replace '^.* '))"
        ) -join ' '
        Status = "$index of $total ($left left) [$('{0:N2}' -f ($index / $total * 100))%]"
        CurrentOperation = "GROUP: $($group.name)"
        PercentComplete = $index / $total * 100
    }
    Write-Progress @WrPrgParam
    $lasttime = Get-Date
    
    # all members progress
    $index3 = 0
    $total3 = $group.member.Count
    $starttime3 = $lasttime3 = Get-Date
    foreach ($member in $group.member) {
        $index2++
        $currtime2 = (Get-Date) - $starttime2
        $avg2 = $currtime2.TotalSeconds / $index2
        $last2 = ((Get-Date) - $lasttime2).TotalSeconds
        $left2 = $total2 - $index2
        $WrPrgParam2 = @{
            Activity = (
                "ALL MEMBERS $(Get-Date -f s)",
                "Total: $($currtime2 -replace '\..*')",
                "Avg: $('{0:N2}' -f $avg2)",
                "Last: $('{0:N2}' -f $last2)",
                "ETA: $('{0:N2}' -f ($avg2 * $left2 / 60))",
                "min ($([string](Get-Date).AddSeconds($avg2*$left2) -replace '^.* '))"
            ) -join ' '
            Status = "$index2 of $total2 ($left2 left) [$('{0:N2}' -f ($index2 / $total2 * 100))%]"
            CurrentOperation = "MEMBER: $member"
            PercentComplete = $index2 / $total2 * 100
            id = 2
        }
        Write-Progress @WrPrgParam2
        $lasttime2 = Get-Date

        # current members progress
        $index3++
        $currtime3 = (Get-Date) - $starttime3
        $avg3 = $currtime3.TotalSeconds / $index3
        $last3 = ((Get-Date) - $lasttime3).TotalSeconds
        $left3 = $total3 - $index3
        $WrPrgParam3 = @{
            Activity = (
                "CURRENT GROUP MEMBERS $(Get-Date -f s)",
                "Total: $($currtime3 -replace '\..*')",
                "Avg: $('{0:N2}' -f $avg3)",
                "Last: $('{0:N2}' -f $last3)",
                "ETA: $('{0:N2}' -f ($avg3 * $left3 / 60))",
                "min ($([string](Get-Date).AddSeconds($avg3*$left3) -replace '^.* '))"
            ) -join ' '
            Status = "$index3 of $total3 ($left3 left) [$('{0:N2}' -f ($index3 / $total3 * 100))%]"
            CurrentOperation = "MEMBER: $member"
            PercentComplete = $index3 / $total3 * 100
            id = 3
        }
        Write-Progress @WrPrgParam3
        $lasttime3 = Get-Date

        $group | select name, @{n='member';e={
            $_.member | ? {
                $_ -notin $notsecuritygroup -and
                $(try {
                    (get-adobject $_ -prop groupcategory).groupcategory -eq 'security'
                } catch {
                    $null = $notsecuritygroup.Add($_)
                })
            }
        }} | ? member
    }
}

$results
Get-ADGroup -Filter {GroupCategory -eq 'security'} -Properties memberof,members |
Where-Object {

        $members = $_.members |
        Get-ADObject |
        Where-Object {
            $_.ObjectClass -eq 'group'
        } | 
        Get-ADGroup |
        Where-Object {
            $_.GroupCategory -eq 'security'
        }

        $memberof = $_.memberof |
        Get-ADObject |
        Where-Object {
            $_.ObjectClass -eq 'group'
        } | 
        Get-ADGroup |
        Where-Object {
            $_.GroupCategory -eq 'security'
        }

        $members -or $memberof

} |
Select-Object -Property DistinguishedName,SamAccountName

This isn’t very efficient, as it results in a lot of calls to AD, but I think it’ll get the list you want.

Here is an example to query AD once and then work with the returned data

$groups = Get-ADGroup -Filter {GroupCategory -eq 'security'} -Properties member,memberof

$groups.member | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.member -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"HasMember"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object * -Unique

$groups.memberof | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.memberof -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"IsMemberOf"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object * -Unique

Results:

distinguishedname                                                     samaccountname                         Type       RelativeGroup                                                  
-----------------                                                     --------------                         ----       -------------                                                  
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Read-only Domain Controllers,CN=Users,DC=domain,DC=local          
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=local           
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Domain Admins,CN=Users,DC=domain,DC=local                         
CN=Administrators,CN=Builtin,DC=domain,DC=local                       Administrators                         HasMember  CN=Domain Admins,CN=Users,DC=domain,DC=local                         
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Cert Publishers,CN=Users,DC=domain,DC=local                       
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Enterprise Admins,CN=Users,DC=domain,DC=local                     
CN=Administrators,CN=Builtin,DC=domain,DC=local                       Administrators                         HasMember  CN=Enterprise Admins,CN=Users,DC=domain,DC=local                     
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Schema Admins,CN=Users,DC=domain,DC=local                         
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Domain Controllers,CN=Users,DC=domain,DC=local                    
CN=Users,CN=Builtin,DC=domain,DC=local                                Users                                  HasMember  CN=Domain Users,CN=Users,DC=domain,DC=local                          
CN=Guests,CN=Builtin,DC=domain,DC=local                               Guests                                 HasMember  CN=Domain Guests,CN=Users,DC=domain,DC=local                         
CN=Domain Controllers,CN=Users,DC=domain,DC=local                     Domain Controllers                     IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Domain Admins,CN=Users,DC=domain,DC=local                          Domain Admins                          IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=local            Group Policy Creator Owners            IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Read-only Domain Controllers,CN=Users,DC=domain,DC=local           Read-only Domain Controllers           IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Cert Publishers,CN=Users,DC=domain,DC=local                        Cert Publishers                        IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Schema Admins,CN=Users,DC=domain,DC=local                          Schema Admins                          IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Enterprise Admins,CN=Users,DC=domain,DC=local                      Enterprise Admins                      IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Domain Admins,CN=Users,DC=domain,DC=local                          Domain Admins                          IsMemberOf CN=Administrators,CN=Builtin,DC=domain,DC=local                      
CN=Enterprise Admins,CN=Users,DC=domain,DC=local                      Enterprise Admins                      IsMemberOf CN=Administrators,CN=Builtin,DC=domain,DC=local                      
CN=Domain Users,CN=Users,DC=domain,DC=local                           Domain Users                           IsMemberOf CN=Users,CN=Builtin,DC=domain,DC=local                               
CN=Domain Guests,CN=Users,DC=domain,DC=local                          Domain Guests                          IsMemberOf CN=Guests,CN=Builtin,DC=domain,DC=local                              

Of course you can limit it to just the distinguishedname and samaccountname if you like, but the above gives you more info at a glance.

$groups = Get-ADGroup -Filter {GroupCategory -eq 'security'} -Properties member,memberof

$groups.member | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.member -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"HasMember"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object distinguishedname, samaccountname -Unique

$groups.memberof | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.memberof -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"IsMemberOf"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object distinguishedname, samaccountname -Unique
Results:
distinguishedname                                                     samaccountname                        
-----------------                                                     --------------                        
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group
CN=Administrators,CN=Builtin,DC=domain,DC=local                       Administrators                        
CN=Users,CN=Builtin,DC=domain,DC=local                                Users                                 
CN=Guests,CN=Builtin,DC=domain,DC=local                               Guests                                
CN=Domain Controllers,CN=Users,DC=domain,DC=local                     Domain Controllers                    
CN=Domain Admins,CN=Users,DC=domain,DC=local                          Domain Admins                         
CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=local            Group Policy Creator Owners           
CN=Read-only Domain Controllers,CN=Users,DC=domain,DC=local           Read-only Domain Controllers          
CN=Cert Publishers,CN=Users,DC=domain,DC=local                        Cert Publishers                       
CN=Schema Admins,CN=Users,DC=domain,DC=local                          Schema Admins                         
CN=Enterprise Admins,CN=Users,DC=domain,DC=local                      Enterprise Admins                     
CN=Domain Users,CN=Users,DC=domain,DC=local                           Domain Users                          
CN=Domain Guests,CN=Users,DC=domain,DC=local                          Domain Guests                         

Here is an example to query AD once and then work with the returned data

$groups = Get-ADGroup -Filter {GroupCategory -eq 'security'} -Properties member,memberof

$groups.member | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.member -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"HasMember"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object * -Unique

$groups.memberof | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.memberof -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"IsMemberOf"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object * -Unique

Results:

distinguishedname                                                     samaccountname                         Type       RelativeGroup                                                  
-----------------                                                     --------------                         ----       -------------                                                  
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Read-only Domain Controllers,CN=Users,DC=domain,DC=local          
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=local           
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Domain Admins,CN=Users,DC=domain,DC=local                         
CN=Administrators,CN=Builtin,DC=domain,DC=local                       Administrators                         HasMember  CN=Domain Admins,CN=Users,DC=domain,DC=local                         
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Cert Publishers,CN=Users,DC=domain,DC=local                       
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Enterprise Admins,CN=Users,DC=domain,DC=local                     
CN=Administrators,CN=Builtin,DC=domain,DC=local                       Administrators                         HasMember  CN=Enterprise Admins,CN=Users,DC=domain,DC=local                     
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Schema Admins,CN=Users,DC=domain,DC=local                         
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group HasMember  CN=Domain Controllers,CN=Users,DC=domain,DC=local                    
CN=Users,CN=Builtin,DC=domain,DC=local                                Users                                  HasMember  CN=Domain Users,CN=Users,DC=domain,DC=local                          
CN=Guests,CN=Builtin,DC=domain,DC=local                               Guests                                 HasMember  CN=Domain Guests,CN=Users,DC=domain,DC=local                         
CN=Domain Controllers,CN=Users,DC=domain,DC=local                     Domain Controllers                     IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Domain Admins,CN=Users,DC=domain,DC=local                          Domain Admins                          IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=local            Group Policy Creator Owners            IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Read-only Domain Controllers,CN=Users,DC=domain,DC=local           Read-only Domain Controllers           IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Cert Publishers,CN=Users,DC=domain,DC=local                        Cert Publishers                        IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Schema Admins,CN=Users,DC=domain,DC=local                          Schema Admins                          IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Enterprise Admins,CN=Users,DC=domain,DC=local                      Enterprise Admins                      IsMemberOf CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local
CN=Domain Admins,CN=Users,DC=domain,DC=local                          Domain Admins                          IsMemberOf CN=Administrators,CN=Builtin,DC=domain,DC=local                      
CN=Enterprise Admins,CN=Users,DC=domain,DC=local                      Enterprise Admins                      IsMemberOf CN=Administrators,CN=Builtin,DC=domain,DC=local                      
CN=Domain Users,CN=Users,DC=domain,DC=local                           Domain Users                           IsMemberOf CN=Users,CN=Builtin,DC=domain,DC=local                               
CN=Domain Guests,CN=Users,DC=domain,DC=local                          Domain Guests                          IsMemberOf CN=Guests,CN=Builtin,DC=domain,DC=local                              

Of course you can limit it to just the distinguishedname and samaccountname if you like, but the above gives you more info at a glance.

$groups = Get-ADGroup -Filter {GroupCategory -eq 'security'} -Properties member,memberof

$groups.member | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.member -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"HasMember"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object distinguishedname, samaccountname -Unique

$groups.memberof | ForEach-Object {
    If ($groups.distinguishedname -contains $_) {
        $current=$_
        $groups | Where-Object {$_.memberof -contains $current} | Select-Object distinguishedname, samaccountname, @{Label='Type';Expression={"IsMemberOf"}}, @{Label='RelativeGroup';Expression={$current}}
    }
} | Select-Object distinguishedname, samaccountname -Unique

Results:

distinguishedname                                                     samaccountname                        
-----------------                                                     --------------                        
CN=Denied RODC Password Replication Group,CN=Users,DC=domain,DC=local Denied RODC Password Replication Group
CN=Administrators,CN=Builtin,DC=domain,DC=local                       Administrators                        
CN=Users,CN=Builtin,DC=domain,DC=local                                Users                                 
CN=Guests,CN=Builtin,DC=domain,DC=local                               Guests                                
CN=Domain Controllers,CN=Users,DC=domain,DC=local                     Domain Controllers                    
CN=Domain Admins,CN=Users,DC=domain,DC=local                          Domain Admins                         
CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=local            Group Policy Creator Owners           
CN=Read-only Domain Controllers,CN=Users,DC=domain,DC=local           Read-only Domain Controllers          
CN=Cert Publishers,CN=Users,DC=domain,DC=local                        Cert Publishers                       
CN=Schema Admins,CN=Users,DC=domain,DC=local                          Schema Admins                         
CN=Enterprise Admins,CN=Users,DC=domain,DC=local                      Enterprise Admins                     
CN=Domain Users,CN=Users,DC=domain,DC=local                           Domain Users                          
CN=Domain Guests,CN=Users,DC=domain,DC=local                          Domain Guests                         

~

Thanks Craig, I’m afraid this one took way too long to get results. We have about 62k User objects in our forest

in my original example i got all the members of the groups and went through that list, but i think a shorter way is to just get all the security groups and see if they are a member of another security group.

it ran exponentially faster than my last attempt, but you’ll have to let me know if it gives problems for your environment

EDIT: taking Curtis’ information in mind (don’t query AD multiple times), i have updated the script

Write-Host 'getting all security groups...'
$groups = Get-ADGroup -Filter "groupcategory -eq 'security'" -Properties memberof | select name, distinguishedname, memberof | ? memberof

$securitygroups = $groups.distinguishedname

# all groups progress
$index = 0
$total = $groups.Count
$starttime = $lasttime = Get-Date

$results = foreach ($group in $groups) {
    # all groups progress
    $index++
    $currtime = (Get-Date) - $starttime
    $avg = $currtime.TotalSeconds / $index
    $last = ((Get-Date) - $lasttime).TotalSeconds
    $left = $total - $index
    $WrPrgParam = @{
        Activity = (
            "ALL GROUPS $(Get-Date -f s)",
            "Total: $($currtime -replace '\..*')",
            "Avg: $('{0:N2}' -f $avg)",
            "Last: $('{0:N2}' -f $last)",
            "ETA: $('{0:N2}' -f ($avg * $left / 60))",
            "min ($([string](Get-Date).AddSeconds($avg*$left) -replace '^.* '))"
        ) -join ' '
        Status = "$index of $total ($left left) [$('{0:N2}' -f ($index / $total * 100))%]"
        CurrentOperation = "GROUP: $($group.name)"
        PercentComplete = $index / $total * 100
    }
    Write-Progress @WrPrgParam
    $lasttime = Get-Date
    
    $group | select name, @{n='memberof';e={$_.memberof | ? {$_ -in $securitygroups}}} | ? memberof
}

$results

Anything that queries AD multiple times in a loop is going to be slower than querying once and working with the returned data. The example I posted shows how to do this and uses much less code to do it.