Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more — all from one place.
🚀 Launch ToolkitIt’s not uncommon for organizations to accumulate distribution groups that no longer serve a purpose. A project ends, a department reorganizes, or simply no one remembers to maintain the group. The result? Empty distribution groups clutter your tenant. These groups add no value, yet they can confuse admins, mislead end users, and in some cases even represent a security oversight.
This script tackles that pain by scanning all distribution groups in your Microsoft 365 tenant, identifying those with no members, and sending the results directly to an administrator via email. This way, IT teams can review, clean up, or repurpose these groups quickly without manually checking each one.
# ===== Find Empty Distribution Groups, Export CSV, and Email to Admin =====
# Requires: Microsoft.Graph module
# Scopes: Group.Read.All, Mail.Send
# --- Email variables ---
$FromUser = "admin@contoso.com" # Sender (must have mailbox)
$To = "it-ops@contoso.com" # Recipient
$Subject = "Empty Distribution Groups report"
$CsvOutDir = "$env:TEMP"
# --- Connect to Microsoft Graph ---
Import-Module Microsoft.Graph -ErrorAction Stop
Connect-MgGraph -Scopes "Group.Read.All","Mail.Send"
# Step 1: Get all mail-enabled groups
$allGroups = Get-MgGroup -All -Property Id, DisplayName, Mail, MailEnabled, SecurityEnabled, GroupTypes, CreatedDateTime
# Step 2: Filter only Distribution Groups (mailEnabled = true, securityEnabled = false, no 'Unified' in groupTypes)
$distributionGroups = $allGroups | Where-Object {
$_.MailEnabled -eq $true -and
$_.SecurityEnabled -eq $false -and
($_.GroupTypes -notcontains "Unified")
}
# Step 3: Find empty distribution groups
Write-Host "`nChecking each Distribution Group for members..." -ForegroundColor Cyan
$emptyGroups = foreach ($group in $distributionGroups) {
$members = Get-MgGroupMember -GroupId $group.Id -ErrorAction SilentlyContinue
if (-not $members) {
[PSCustomObject]@{
DisplayName = $group.DisplayName
Mail = $group.Mail
GroupId = $group.Id
CreatedDate = $group.CreatedDateTime
}
}
}
# Ensure we have a collection, even if none found
if (-not $emptyGroups) { $emptyGroups = @() }
# Step 4: Output results to console (as before)
if ($emptyGroups.Count -gt 0) {
Write-Host "`nEmpty Distribution Groups found:`n" -ForegroundColor Green
$emptyGroups | Format-Table DisplayName, Mail, GroupId, CreatedDate
} else {
Write-Host "`nNo empty distribution groups found." -ForegroundColor Yellow
}
# --- Export to CSV ---
if (-not (Test-Path -Path $CsvOutDir)) { New-Item -ItemType Directory -Path $CsvOutDir | Out-Null }
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$csvPath = Join-Path $CsvOutDir ("Empty_Distribution_Groups_{0}.csv" -f $ts)
$emptyGroups | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
# --- Prepare HTML Body ---
$summaryHtml = @"
<html>
<body style='font-family:Segoe UI,Arial,sans-serif'>
<h3>Empty Distribution Groups Report</h3>
<p>Total empty distribution groups: <b>$($emptyGroups.Count)</b></p>
<p>The full list is attached as a CSV.</p>
</body>
</html>
"@
# --- Prepare Attachment ---
$fileBytes = [System.IO.File]::ReadAllBytes($csvPath)
$base64Content = [System.Convert]::ToBase64String($fileBytes)
$csvFileName = [System.IO.Path]::GetFileName($csvPath)
$attachment = @{
"@odata.type" = "#microsoft.graph.fileAttachment"
name = $csvFileName
contentBytes = $base64Content
contentType = "text/csv"
}
# --- Prepare and Send Email ---
$mail = @{
message = @{
subject = "${Subject}"
body = @{
contentType = "HTML"
content = $summaryHtml
}
toRecipients = @(@{ emailAddress = @{ address = $To } })
attachments = @($attachment)
}
saveToSentItems = $true
}
Send-MgUserMail -UserId $FromUser -BodyParameter $mail
Write-Host "Done. CSV saved at: $csvPath" -ForegroundColor Green
The script uses the Group.Read.All scope to read distribution groups and Mail.Send to deliver the report to the administrator.
From all mail-enabled groups, it keeps only those where:
Each distribution group is checked with Get-MgGroupMember. If no members are found, the group is flagged as empty.
Results are saved into a timestamped CSV file in the system’s temp directory.
An HTML summary shows how many empty distribution groups exist. The CSV is attached and sent to the admin.
Error | Cause | Solution |
---|---|---|
Authorization_RequestDenied | Missing Graph scopes | Connect with both Group.Read.All and Mail.Send. Admin consent may be required. |
Get-MgGroup not recognized | Microsoft Graph module missing | Run Install-Module Microsoft.Graph -Scope CurrentUser. |
CSV not created | Invalid $CsvOutDir path | Ensure the directory exists or set $CsvOutDir to a valid folder. |
Email not sent | $FromUser has no mailbox | Use a mailbox-enabled account as the sender. |
Empty results though groups exist | Filter excludes Unified/M365 groups | This script only checks distribution groups, not M365 or security groups. |
Empty distribution groups serve no purpose and can complicate directory management. This script provides a reliable way to identify them, export results, and send a neat CSV report to administrators. By scheduling it regularly and enhancing it with owner or age details, you can streamline directory hygiene, reduce clutter, and keep your Microsoft 365 environment secure and well-organized.
© m365corner.com. All Rights Reserved. Design by HTML Codex