Public/Get-DistributionGroupAudit.ps1

function Get-DistributionGroupAudit {
    <#
    .SYNOPSIS
        Audits distribution groups for migration planning.
 
    .DESCRIPTION
        Inventories distribution groups, dynamic distribution groups, and
        mail-enabled security groups with:
        - Member count
        - Group type
        - Managed by
        - Nested group detection
        - Empty group detection
        - External member detection
 
    .PARAMETER IncludeEmpty
        Include groups with zero members. Default excludes them.
 
    .EXAMPLE
        Get-DistributionGroupAudit
 
    .EXAMPLE
        Get-DistributionGroupAudit -IncludeEmpty | Where-Object MemberCount -eq 0 | Export-Csv EmptyGroups.csv
    #>

    [CmdletBinding()]
    param(
        [switch]$IncludeEmpty
    )

    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    # Standard Distribution Groups
    Write-Verbose "Getting distribution groups..."
    $distGroups = Get-DistributionGroup -ResultSize Unlimited

    foreach ($group in $distGroups) {
        $members = @(Get-DistributionGroupMember -Identity $group.Identity -ResultSize Unlimited -ErrorAction SilentlyContinue)
        $memberCount = $members.Count

        if (-not $IncludeEmpty -and $memberCount -eq 0) { continue }

        $nestedGroups = @($members | Where-Object RecipientType -like '*Group*').Count
        $externalMembers = @($members | Where-Object RecipientType -eq 'MailContact').Count

        $results.Add([PSCustomObject]@{
            Name               = $group.DisplayName
            PrimarySmtp        = $group.PrimarySmtpAddress
            GroupType          = if ($group.GroupType -match 'Security') { 'Mail-Enabled Security' } else { 'Distribution' }
            MemberCount        = $memberCount
            NestedGroups       = $nestedGroups
            ExternalMembers    = $externalMembers
            ManagedBy          = ($group.ManagedBy | ForEach-Object { $_.Name }) -join '; '
            AcceptFromExternal = -not $group.RequireSenderAuthenticationEnabled
            Hidden             = $group.HiddenFromAddressListsEnabled
            MigrationNotes     = if ($nestedGroups -gt 0) { 'Has nested groups - verify after migration' }
                                 elseif ($externalMembers -gt 0) { 'Has external contacts' }
                                 else { '' }
        })
    }

    # Dynamic Distribution Groups
    Write-Verbose "Getting dynamic distribution groups..."
    $dynGroups = Get-DynamicDistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue

    foreach ($group in $dynGroups) {
        $previewMembers = @(Get-Recipient -RecipientPreviewFilter $group.RecipientFilter -ErrorAction SilentlyContinue)

        $results.Add([PSCustomObject]@{
            Name               = $group.DisplayName
            PrimarySmtp        = $group.PrimarySmtpAddress
            GroupType          = 'Dynamic'
            MemberCount        = $previewMembers.Count
            NestedGroups       = 0
            ExternalMembers    = 0
            ManagedBy          = ($group.ManagedBy | ForEach-Object { $_.Name }) -join '; '
            AcceptFromExternal = $false
            Hidden             = $group.HiddenFromAddressListsEnabled
            MigrationNotes     = 'Dynamic group - verify recipient filter works post-migration'
        })
    }

    $results | Sort-Object GroupType, Name
}