Public/Get-MailGroupWithMember.ps1

function Get-MailGroupWithMembers {
<#
.SYNOPSIS
  List Distribution Groups and Mail-Enabled Security Groups in AD with members.
 
.DESCRIPTION
  - Includes all Distribution Groups (regardless of mail attributes)
  - Includes only Security Groups that are mail-enabled (mail or proxyAddresses present)
  - Labels type and lists members (direct or recursive)
  - Optionally exports results to CSV
 
.PARAMETER SearchBase
  Optional DN to scope the search (e.g., "OU=Groups,DC=corp,DC=contoso,DC=com")
 
.PARAMETER Recursive
  If provided, expands membership recursively.
 
.PARAMETER ExportCsvPath
  Optional file path to export the flattened membership list as CSV.
 
.EXAMPLE
  .\Get-MailGroupsWithMembers.ps1 -Recursive -ExportCsvPath C:\Temp\Groups.csv
 
.EXAMPLE
  .\Get-MailGroupsWithMembers.ps1 -SearchBase "OU=Email,DC=example,DC=com"
#>


[CmdletBinding()]
param(
    [string]$SearchBase,
    [switch]$Recursive,
    [string]$ExportCsvPath
)

# Ensure ActiveDirectory module is available
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
    Throw "The ActiveDirectory module is required. Install RSAT or run on a domain-joined management host with RSAT."
}
Import-Module ActiveDirectory -ErrorAction Stop

# Build the filter:
# - Include ALL Distribution groups (regardless of mail presence)
# - Include ONLY Security groups that are mail-enabled (mail or proxyAddresses)
$groupFilter = '((groupCategory -eq "Distribution") -or ((groupCategory -eq "Security") -and ((mail -like "*") -or (proxyAddresses -like "*"))))'

$commonProps = @('mail','proxyAddresses','groupCategory','groupScope','displayName','managedBy')

$groupParams = @{
    Filter      = $groupFilter
    Properties  = $commonProps
}
if ($SearchBase) { $groupParams['SearchBase'] = $SearchBase }

$groups = Get-ADGroup @groupParams | Sort-Object Name

# Helper: Extract primary SMTP (uppercase 'SMTP:')
function Get-PrimarySmtp {
    param([string[]]$ProxyAddresses)
    if (-not $ProxyAddresses) { return $null }
    $primary = $ProxyAddresses | Where-Object { $_ -cmatch '^SMTP:' } | Select-Object -First 1
    if ($primary) { return $primary.Substring(5) }
    return $null
}

# Collect results (flattened: one row per member per group)
$results = New-Object System.Collections.Generic.List[object]

foreach ($g in $groups) {
    $groupType = if ($g.GroupCategory -eq 'Security') { 'Mail-Enabled Security Group' } else { 'Distribution Group' }
    $primarySmtp = Get-PrimarySmtp -ProxyAddresses $g.proxyAddresses

    # Pull members (direct or recursive)
    try {
        $memberParams = @{ Identity = $g.DistinguishedName }
        if ($Recursive.IsPresent) { $memberParams['Recursive'] = $true }
        $members = Get-ADGroupMember @memberParams -ErrorAction Stop
    }
    catch {
        Write-Warning "Failed to get members for group '$($g.Name)': $($_.Exception.Message)"
        $members = @()
    }

    if (-not $members -or $members.Count -eq 0) {
        # Emit at least one row so empty groups are visible
        $results.Add([pscustomobject]@{
            GroupName              = $g.Name
            GroupSamAccountName    = $g.SamAccountName
            GroupType              = $groupType
            GroupScope             = $g.GroupScope
            GroupEmail             = $g.mail
            GroupPrimarySmtp       = $primarySmtp
            GroupDisplayName       = $g.DisplayName
            GroupManagedByDN       = $g.ManagedBy
            GroupDistinguishedName = $g.DistinguishedName

            MemberName             = $null
            MemberSamAccountName   = $null
            MemberType             = $null
            MemberDistinguishedName= $null
        })
        continue
    }

    foreach ($m in $members) {
        # m can be user, group, computer, contact
        $results.Add([pscustomobject]@{
            GroupName              = $g.Name
            GroupSamAccountName    = $g.SamAccountName
            GroupType              = $groupType
            GroupScope             = $g.GroupScope
            GroupEmail             = $g.mail
            GroupPrimarySmtp       = $primarySmtp
            GroupDisplayName       = $g.DisplayName
            GroupManagedByDN       = $g.ManagedBy
            GroupDistinguishedName = $g.DistinguishedName

            MemberName             = $m.Name
            MemberSamAccountName   = $m.SamAccountName
            MemberType             = $m.objectClass
            MemberDistinguishedName= $m.DistinguishedName
        })
    }
}

# Emit to console (table) and optionally export
$results |
    Sort-Object GroupName, MemberType, MemberSamAccountName |
    Format-Table GroupName, GroupType, GroupScope, GroupPrimarySmtp, MemberType, MemberSamAccountName -AutoSize

if ($ExportCsvPath) {
    $dir = Split-Path -Path $ExportCsvPath -Parent
    if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
    $results | Export-Csv -Path $ExportCsvPath -NoTypeInformation -Encoding UTF8
    Write-Host "Exported results to: $ExportCsvPath" -ForegroundColor Green
}

# Also produce a quick summary by group (counts by member type)
$summary = $results |
    Group-Object GroupName |
    ForEach-Object {
        $rows = $_.Group
        [pscustomobject]@{
            GroupName        = $_.Name
            GroupType        = ($rows | Select-Object -First 1).GroupType
            GroupScope       = ($rows | Select-Object -First 1).GroupScope
            PrimarySmtp      = ($rows | Select-Object -First 1).GroupPrimarySmtp
            MembersTotal     = ($rows | Where-Object { $_.MemberSamAccountName }).Count
            UsersCount       = ($rows | Where-Object { $_.MemberType -eq 'user' }).Count
            GroupsCount      = ($rows | Where-Object { $_.MemberType -eq 'group' }).Count
            ContactsCount    = ($rows | Where-Object { $_.MemberType -eq 'contact' }).Count
            ComputersCount   = ($rows | Where-Object { $_.MemberType -eq 'computer' }).Count
        }
    } | Sort-Object GroupName

Write-Host "`nSummary (per group):" -ForegroundColor Cyan
$summary | Format-Table -AutoSize
}