PowerShell Exchange Online Distribution Group Audit
─□✕

PowerShell Exchange Online Distribution Group Audit

PowerShell Tips Editor 2 min read
PowerShell Exchange Online Distribution Group Audit

Distribution groups accumulate silently over years: project lists whose projects ended, team aliases where the team disbanded, external-sender-enabled groups whose owners have left the company. Without active governance, each one is a potential email security risk or a compliance gap. A PowerShell audit of your Exchange Online distribution groups surfaces every group’s membership count, ownership status, and external sender configuration in a single exportable report.

Quick Answer

Connect with Connect-ExchangeOnline, enumerate groups with Get-DistributionGroup -ResultSize Unlimited, pull members with Get-DistributionGroupMember, and flag groups where ManagedBy is empty or RequireSenderAuthenticationEnabled is $false for immediate review.

Connecting to Exchange Online and Getting All Groups

The Exchange Online PowerShell module (ExchangeOnlineManagement) is required. Install it once from PSGallery if not already present.

# Install if needed
# Install-Module ExchangeOnlineManagement -Scope CurrentUser

Import-Module ExchangeOnlineManagement

# Connect — opens browser for MFA if required
Connect-ExchangeOnline -UserPrincipalName "[email protected]" -ShowProgress $true

# Get all distribution groups (not Microsoft 365 Groups)
$groups = Get-DistributionGroup -ResultSize Unlimited |
          Where-Object { $_.RecipientTypeDetails -eq "MailUniversalDistributionGroup" }

Write-Host "Found $($groups.Count) distribution groups."

Note the RecipientTypeDetails filter. Get-DistributionGroup also returns mail-enabled security groups. Adjust the filter if you want to include those in the audit. Microsoft 365 Groups require Get-UnifiedGroup — they are a different cmdlet family entirely.

Enumerating Members with Get-DistributionGroupMember

Member counts and the list of members are the first governance data points. Large groups — especially those with external members — deserve extra scrutiny.

$groupData = foreach ($group in $groups) {
    $members = Get-DistributionGroupMember -Identity $group.Identity `
                                           -ResultSize Unlimited `
                                           -ErrorAction SilentlyContinue

    $externalMembers = $members |
        Where-Object { $_.RecipientTypeDetails -eq "MailContact" -or
                       $_.ExternalEmailAddress -ne $null }

    [PSCustomObject]@{
        DisplayName      = $group.DisplayName
        Alias            = $group.Alias
        MemberCount      = $members.Count
        ExternalMembers  = $externalMembers.Count
        PrimarySmtp      = $group.PrimarySmtpAddress
        ManagedBy        = ($group.ManagedBy -join "; ")
        HasOwner         = ($group.ManagedBy.Count -gt 0)
        RequireAuth      = $group.RequireSenderAuthenticationEnabled
        HiddenFromGAL    = $group.HiddenFromAddressListsEnabled
    }
}

Write-Host "Processed $($groupData.Count) groups."

Identifying Groups with No Assigned Owner

Ownerless groups are a governance red flag: no one is responsible for reviewing membership or approving sender requests.

$noOwner = $groupData | Where-Object { -not $_.HasOwner }

Write-Host "Groups with no owner: $($noOwner.Count)"

$noOwner | Select-Object DisplayName, Alias, MemberCount, PrimarySmtp |
    Format-Table -AutoSize

Assign an interim owner or deprecate ownerless groups. You can set an owner with Set-DistributionGroup -Identity "alias" -ManagedBy "[email protected]" directly from the audit script.

Checking RequireSenderAuthenticationEnabled Setting

When RequireSenderAuthenticationEnabled is $false, anyone on the internet can send to the group — a frequent spam and phishing vector. Audit how many groups have this open.

$openToExternal = $groupData | Where-Object { $_.RequireAuth -eq $false }

Write-Host "Groups open to external senders: $($openToExternal.Count)"

foreach ($g in $openToExternal) {
    Write-Host "  OPEN: $($g.DisplayName) ($($g.Alias)) — $($g.MemberCount) members" `
               -ForegroundColor Yellow
}

# Remediate: lock down a specific group
# Set-DistributionGroup -Identity "dl-allstaff" `
#     -RequireSenderAuthenticationEnabled $true

Reporting Last Delivery Date from Message Trace

Groups that have not received email in months are candidates for decommissioning. Get-MessageTrace provides delivery history but has a 10-day lookback limit for standard queries.

$endDate   = Get-Date
$startDate = $endDate.AddDays(-10)

$groupData = $groupData | ForEach-Object {
    $smtpAddr = $_.PrimarySmtp
    $trace    = Get-MessageTrace -RecipientAddress $smtpAddr `
                                  -StartDate $startDate `
                                  -EndDate $endDate `
                                  -ErrorAction SilentlyContinue |
                Select-Object -First 1

    $_ | Add-Member -MemberType NoteProperty `
                    -Name LastDelivery `
                    -Value ($trace.Received ?? "No mail in 10 days") `
                    -Force
    $_
}

# For older data use Start-HistoricalSearch (async, results via email)
# Start-HistoricalSearch -ReportTitle "DL Audit" -StartDate (Get-Date).AddDays(-30) ...

Exporting the Full Group Audit to CSV

Combine all collected data into a final export for review meetings and compliance records.

$reportPath = "DLAudit-$(Get-Date -Format yyyyMMdd).csv"

$groupData | Select-Object DisplayName, Alias, PrimarySmtp,
                            MemberCount, ExternalMembers,
                            HasOwner, ManagedBy,
                            RequireAuth, HiddenFromGAL, LastDelivery |
    Sort-Object HasOwner, DisplayName |
    Export-Csv $reportPath -NoTypeInformation -Encoding UTF8

Write-Host "Audit exported: $reportPath ($($groupData.Count) groups)"

# Disconnect cleanly
Disconnect-ExchangeOnline -Confirm:$false

Common Errors

  • Get-MessageTrace maximum lookback of 10 days: The cmdlet hard-limits results to the last 10 days. For historical delivery data beyond that window, use Start-HistoricalSearch, which runs asynchronously and delivers results to an admin mailbox — or use the Microsoft 365 Compliance center Message Trace for up to 90 days.
  • Mixing Distribution Groups and Microsoft 365 Groups: Get-DistributionGroup does not return Microsoft 365 Groups (formerly Office 365 Groups). Those require Get-UnifiedGroup. If your audit needs to cover both, run both cmdlets and combine the results — the ownership, membership, and external sender properties use different names between the two cmdlet families.

Related Cmdlets / See Also

Wrapping Up

A quarterly distribution group audit takes minutes with this script and surfaces ownerless groups, open external relays, and dormant lists before they become compliance findings. Schedule it, export to CSV, and route flagged groups to their business owners for disposition decisions.

Send-Item -To