Private/ADGroupVerify.ps1

function ADGroupVerify {
<#
.SYNOPSIS
    Find a group (by CN) in multiple domains and return *all* members
    using pure ADSI range-retrieval.
 
.PARAMETER GroupName
    Group common name (CN).
 
.PARAMETER Domains
    DNS domains to search, e.g. @("lab.company.com","test.company.com").
 
.OUTPUTS
    PSCustomObject { Domain ; Members } – one per domain where the group exists.
    If group not found anywhere, writes a message.
#>

    param(
        [Parameter(Mandatory)][string]   $GroupName,
        [Parameter(Mandatory)][string[]] $Domains
    )

    $escapedCN = $GroupName -replace '([\\*()\0])', { '\{0:x2}' -f [byte][char]$args[0].Value }
    $groupFilter = "(&(objectClass=group)(cn=$escapedCN))"

    $found = $false

    foreach ($domain in $Domains) {
        try {
            # Build "DC=x,DC=y" path for the GC of this domain
            $dcParts = $domain -split '\.' | ForEach-Object { "DC=$_" }
            $dcPath  = $dcParts -join ','
            $gcRoot  = [ADSI]"GC://$dcPath"

            $searcher               = New-Object DirectoryServices.DirectorySearcher
            $searcher.SearchRoot    = $gcRoot
            $searcher.SearchScope   = 'Subtree'
            $searcher.PageSize      = 100
            $searcher.Filter        = $groupFilter
            $searcher.PropertiesToLoad.Add('distinguishedName') | Out-Null

            # ---- locate the group object first ----
            $grp = $searcher.FindOne()
            if (-not $grp) { continue }   # not in this domain

            $found = $true
            $members = @()

            # ---- dynamic range retrieval loop ----
            $rangeStart = 0
            $rangeSize  = 1500   # AD’s hard limit per chunk
            while ($true) {
                $rangeAttr = "member;range=$rangeStart-*"
                $searcher.PropertiesToLoad.Clear()
                $searcher.PropertiesToLoad.Add($rangeAttr) | Out-Null

                $chunk = $searcher.FindOne()
                if (-not $chunk) { break }

                # The server returns the *actual* attribute name containing the numbers
                $attrName = $chunk.Properties.PropertyNames |
                            Where-Object { $_ -like "member;range=$rangeStart-*" }

                if (-not $attrName) { break }  # nothing else

                $members += $chunk.Properties[$attrName]

                if ($attrName -match 'member;range=\d+-(\d+|\*)$') {
                    $endVal = $Matches[1]
                    if ($endVal -eq '*') {
                        break   # last chunk received
                    }
                    else {
                        $rangeStart = [int]$endVal + 1  # next chunk start
                    }
                }
                else { break }
            }

            [pscustomobject]@{
                Domain  = $domain
                Members = $members
                Membercount = $members.Count
            }
        }
        catch {
            Write-Warning "Error searching $domain : $($_.Exception.Message)"
        }
    }

    if (-not $found) {
        Write-Output "Group '$GroupName' not found in any supplied domain."
    }
}