Public/Get-ALHADCircularNestedGroup.ps1

<#PSScriptInfo
 
.VERSION 1.0.0
 
.GUID 27db6c7c-0d12-4235-8ac8-0ba11affcd01
 
.AUTHOR Dieter Koch
 
.COMPANYNAME
 
.COPYRIGHT (c) 2021-2023 Dieter Koch
 
.TAGS
 
.LICENSEURI https://github.com/admins-little-helper/ALH/blob/main/LICENSE
 
.PROJECTURI https://github.com/admins-little-helper/ALH
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
    1.0.0
    Initial release
 
#>



<#
 
.DESCRIPTION
 Contains a function to find circular nested groups in Active Directory.
 
.LINK
https://github.com/admins-little-helper/ALH
 
#>



function Get-ALHADCircularNestedGroup {
    <#
    .SYNOPSIS
    Find circular nested groups in Active Directory.
 
    .DESCRIPTION
    The 'Get-ALHADCircularNestedGroup' function searches for instances of circular nested groups in Active Directory.
 
    Sometimes it happens that circular nested groups get created accidentally.
    For example GroupA has GroupB as member. GroupB has GroupC as member. And GroupC has GroupA as member.
    This function helps to identify these conflicts.
 
    .PARAMETER SearchBase
    One ore more names of organizational unites to search in recursively for nested groups.
    If not specified, the entire domain will be searched.
 
    .EXAMPLE
    Get-ALHADCircularNestedGroup -SearchBase 'OU=Groups,DC=contoso,DC=com' -Verbose
 
    Find all circular groups in two different organizational units and show verbose messages.
 
    .EXAMPLE
    Get-ALHADCircularNestedGroup
 
    Find all circular groups in the domain.
 
    .INPUTS
    System.String for parameter 'SearchBase'
 
    .OUTPUTS
    PSCustomObject
 
    .NOTES
    Author: Dieter Koch
    Email: diko@admins-little-helper.de
 
    .LINK
    https://github.com/admins-little-helper/ALH/blob/main/Help/Get-ALHADCircularNestedGroup.txt
    #>


    [CmdLetBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [String[]]$SearchBase
    )

    begin {
        $StartDateTime = Get-Date

        if ( [string]::IsNullOrEmpty($SearchBase)) {
            try {
                $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
                Write-Verbose "Searching for groups in entire AD domain '$($Domain.Name)'"
                $SearchBase = ($Domain.GetDirectoryEntry()).DistinguishedName
            }
            catch {
                $_
                break
            }
        }
    }

    process {
        foreach ($SearchBaseElement in $SearchBase) {
            try {
                Write-Verbose -Message "Trying to get groups from AD..."
                $AllGroups = Get-ALHADGroupMemberHT -SearchBase $SearchBaseElement
                Write-Verbose "# Groups found $(($AllGroups | Measure-Object).Count)"
            }
            catch {
                throw "Failed getting circular group membership: $_"
            }

            try {
                $GroupMembers = @{}

                # Enumerate groups and populate hashtable.
                # The key value will be the Distinguished Name of the group.
                # The item value will be an array of the Distinguished Names of all members of the group that are groups.
                # The item value starts out as an empty array, since we don't know yet which members are groups.
                foreach ($Group in $AllGroups) {
                    $DN = [String]$Group.properties.Item('distinguishedName')
                    Write-Debug -Message "Adding group with DN to hashtable: $DN"
                    $GroupMembers.Add($DN, @())
                }

                # Now enumerate the groups again to populate the item value arrays.
                foreach ($Group in $AllGroups) {
                    $DN = [String]$Group.properties.Item('distinguishedName')
                    $Members = @($Group.properties.Item('member'))

                    foreach ($Member in $Members) {
                        If ($GroupMembers.ContainsKey($Member)) {
                            Write-Debug -Message "Adding member to group --> $DN"
                            Write-Debug -Message "Group member -->--> $Member"
                            $GroupMembers[$DN] += $Member
                        }
                    }
                }

                $NestedGroups = foreach ($Group in $GroupMembers.Keys) {
                    Get-ALHNestetdGroup -Identity $Group -Parent @($Group) -GroupMember $GroupMembers
                }

                $NestedGroups

                Write-Verbose -Message "Found '$(($NestedGroups | Measure-Object).Count)' nested groups in $SearchBaseElement"
            }
            catch {
                throw "Failed retrieving circular group membership: $_"
            }
        }
    }

    end {
        Write-Verbose -Message "Search finished"
        Write-Verbose -Message "Elapes time in seconds: $($(Get-Date) - $StartDateTime)"
    }
}


#region EndOfScript
<#
################################################################################
################################################################################
#
# ______ _ __ _____ _ _
# | ____| | | / _| / ____| (_) | |
# | |__ _ __ __| | ___ | |_ | (___ ___ _ __ _ _ __ | |_
# | __| | '_ \ / _` | / _ \| _| \___ \ / __| '__| | '_ \| __|
# | |____| | | | (_| | | (_) | | ____) | (__| | | | |_) | |_
# |______|_| |_|\__,_| \___/|_| |_____/ \___|_| |_| .__/ \__|
# | |
# |_|
################################################################################
################################################################################
# created with help of http://patorjk.com/software/taag/
#>

#endregion