Home > Net >  Powershell: Find all nested groups
Powershell: Find all nested groups

Time:11-19

Using get-adgroupmember to find the immediate members of a group is well-known. Likewise, adding the -recursive parameter is a well-known way to find all users that are members of a group. Is there a way to find all groups that are members of a group?

Background: One of my customers have had a nasty habit of using nested groups for access management. The worst case sees six levels of nested groups, each level having a significant number of groups as members.

CodePudding user response:

Revised answer

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 (untested):

function Get-NestedADGroup {
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [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
            $adGroup
            $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 'TheGroupToInspect'

# output just the names
$result.Name

CodePudding user response:

Another take to Theo's helpful answer using a Stack<T> instead of recursion.

Note, this function will output unique objects, i.e.: if a user was a member of more than one nested group, said user will be only outputted once (same applies for any ObjectClass).

function Get-ADGroupMemberRecursive {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [alias('DistinguishedName')]
        [string] $Identity,

        [Parameter()]
        [string[]] $Properties,

        [Parameter()]
        [string] $Server
    )

    begin {
        $adGroupParams = @{
            Properties = 'member'
        }

        $adObjParams = @{ }
        if($PSBoundParameters.ContainsKey('Properties')) {
            $adObjParams['Properties'] = $Properties
        }
        if($PSBoundParameters.ContainsKey('Server')) {
            $adObjParams['Server'] = $Server
            $adGroupParams['Server'] = $Server
        }
    }
    process {
        $hash  = [Collections.Generic.HashSet[guid]]::new()
        $stack = [Collections.Generic.Stack[string]]::new()
        $stack.Push($Identity)

        while($stack.Count) {
            $adGroupParams['Identity'] = $stack.Pop()
            foreach($member in (Get-ADGroup @adGroupParams).member) {
                $adObjParams['Identity'] = $member
                foreach($item in Get-ADObject @adObjParams) {
                    if($hash.Add($item.ObjectGuid)) {
                        if($item.ObjectClass -eq 'group') {
                            $stack.Push($item.DistinguishedName)
                        }
                        $item
                    }
                }
            }
        }
    }
}

Usage is fairly straight forward:

# positional binding
Get-ADGroupMemberRecursive groupNameHere

# or through pipeline
Get-ADGroup groupNameHere | Get-ADGroupMemberRecursive
  • Related