Public/Get-ADGroupsAndMembers.ps1

function Get-ADGroupsAndMembers {
    [CmdletBinding()]
    param(
        [switch]$Export,
        [string]$ExportPath = $script:Config.ExportPath,
        [string]$DomainController = $env:LOGONSERVER.TrimStart("\\")  # Get current logged-on DC
    )
    
    try {
        Write-Log "Retrieving groups and members from $DomainController..." -Level Info
        Show-ProgressHelper -Activity "AD Inventory" -Status "Initializing group retrieval..."
        
        # Get the current user's domain
        $currentDomain = $env:USERDOMAIN
        Write-Log "Current domain: $currentDomain" -Level Info
        
        # Parameters for AD cmdlets
        $adParams = @{
            Server      = $DomainController
            ErrorAction = 'Stop'
        }
        
        Show-ProgressHelper -Activity "AD Inventory" -Status "Getting groups..."
        
        # Get groups with basic properties first
        $groups = Invoke-WithRetry -ScriptBlock {
            Get-ADGroup @adParams -Filter * -Properties Description, Created, Modified, DistinguishedName
        }
        
        $groupObjects = @()
        $totalGroups = ($groups | Measure-Object).Count
        $currentGroup = 0
        
        foreach ($group in $groups) {
            $currentGroup++
            $percentComplete = ($currentGroup / $totalGroups) * 100
            
            Show-ProgressHelper -Activity "AD Inventory" `
                -Status "Processing group $currentGroup of $totalGroups" `
                -CurrentOperation $group.Name `
                -PercentComplete $percentComplete
            
            try {
                # Get members with timeout protection
                $members = @()
                $memberCount = 0
                
                # Use a timeout mechanism for member retrieval
                $memberJob = Start-Job -ScriptBlock {
                    param($groupDN, $server)
                    Get-ADGroup -Identity $groupDN -Properties Members -Server $server |
                    Select-Object -ExpandProperty Members
                } -ArgumentList $group.DistinguishedName, $DomainController
                
                # Wait up to 30 seconds for member retrieval
                if (Wait-Job $memberJob -Timeout 30) {
                    $memberDNs = Receive-Job $memberJob
                    $memberCount = ($memberDNs | Measure-Object).Count
                    
                    # Only process first 100 members for large groups
                    if ($memberCount -gt 100) {
                        $memberDNs = $memberDNs | Select-Object -First 100
                        Write-Log "Group $($group.Name) has more than 100 members. Only processing first 100." -Level Warning
                    }
                    
                    foreach ($memberDN in $memberDNs) {
                        try {
                            $member = Get-ADObject $memberDN -Server $DomainController -Properties name, objectClass -ErrorAction Stop
                            $members += "$($member.objectClass):$($member.name)"
                        }
                        catch {
                            $members += "Inaccessible:$memberDN"
                        }
                    }
                }
                else {
                    Write-Log "Timeout while retrieving members for group $($group.Name)" -Level Warning
                    $members = @("Timeout occurred while retrieving members")
                }
                
                Remove-Job $memberJob -Force
                
            }
            catch {
                Write-Log "Error processing group $($group.Name): $($_.Exception.Message)" -Level Warning
                $members = @("Error retrieving members")
            }
            
            # Extract OU path
            $ouPath = ($group.DistinguishedName -split ',(?=OU=)' | Where-Object { $_ -match '^OU=' }) -join ','
            if (-not $ouPath) { $ouPath = "No OU (Root)" }
            
            $groupObjects += [PSCustomObject]@{
                Name              = $group.Name
                Description       = $group.Description
                MemberCount       = $memberCount
                Members           = ($members | Select-Object -First 100) -join "; "
                Created           = $group.Created
                Modified          = $group.Modified
                DistinguishedName = $group.DistinguishedName
                OUPath            = $ouPath
                AccessStatus      = if ($members -contains "Error retrieving members") { "Partial Access" } else { "Full Access" }
            }
        }
        
        # Generate and display statistics
        $stats = Get-CollectionStatistics -Data $groupObjects -ObjectType "Groups"
        Write-Host "`n=== Group Collection Statistics ==="
        Write-Host "Total Groups: $($stats.TotalCount)"
        Write-Host "Accessible Groups: $(($groupObjects | Where-Object { $_.AccessStatus -eq 'Full Access' }).Count)"
        Write-Host "Partially Accessible Groups: $(($groupObjects | Where-Object { $_.AccessStatus -eq 'Partial Access' }).Count)"
        Write-Host "`nDistribution by OU:"
        $stats.OUDistribution.GetEnumerator() | Sort-Object Name | ForEach-Object {
            Write-Host ("{0,-50} : {1,5}" -f $_.Key, $_.Value)
        }
        
        if ($Export) {
            Show-ProgressHelper -Activity "AD Inventory" -Status "Exporting group data..."
            if (-not (Test-Path $ExportPath)) {
                New-Item -ItemType Directory -Path $ExportPath -Force
            }
            $exportFile = Join-Path $ExportPath "Groups_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
            $groupObjects | Export-Csv $exportFile -NoTypeInformation
            Write-Log "Groups exported to $exportFile" -Level Info
        }
        
        Show-ProgressHelper -Activity "AD Inventory" -Status "Group retrieval complete" -Completed
        return $groupObjects
        
    }
    catch {
        Write-Log "Error retrieving groups: $($_.Exception.Message)" -Level Error
        Show-ErrorBox "Unable to retrieve groups or group members. Check permissions."
    }
}