To start with, this should really be supplied as a built-in report by Microsoft, but I really would like to get an idea of my potential mailbox issues.
A script I'm working on, it isn't fully tested/worked out so use at your own risk. We have a well over a hundred accepted domains, so I needed to have it handle querying it for me. Our tenant is really large with huge volumes so it's really hard to run even a full day, but I've been poking at it here and there to get a report.
You are limited by number of days you can pull with this command without starting a job and returning to it later. I might poke at that option in the future, but for now, just focusing on the "easy" method. Also be aware, this doesn't take into consideration something that was sourced "outside" of the tenant (IE through a relay that would not be impacted by this) - I haven't taken the time to see if that can be filtered in the standard Message Trace option. This is where JeremyTBradshaw solution might be better.
Updated recently with some error handling on the paging side. There's an $hoursToChunk variable, increasing it increases the amount it will return each Page, but if you exceed 200 pages, it will error out (cannot retrieve more than 200 pages with Get-MessageTrace, even though documentation says it is 1000) [9/20/24] - Consumes quite a bit of memory and is quite resource intensive the longer it runs, there's more optimization for memory that can be done.
$daysToLookBack = 10 # Change to how many days to run against - this will greatly impact the speed of the script
$hoursPerChunk = 2 # Change to the number of hours to break it apart by (1-24) - Higher Number more memory but might be faster at risk of erroring script
# Connect to Exchange Online
Connect-ExchangeOnline
# Retrieve the list of authorized domains to determine Internal Messages
$authorizedDomains = (Get-AcceptedDomain | Where-Object {$_.DomainType -eq 'Authoritative'}).DomainName
# Initialize variables for pagination
$pageSize = 5000
$maxRetries = 3
$groupedBySenderAndDate = @()
# Calculate the start date based on the days to look back
$startDate = (Get-Date).AddDays(-$daysToLookBack)
# Loop through each day
for ($i = 0; $i -lt $daysToLookBack; $i++) {
$currentDate = $startDate.AddDays($i)
# Loop through each chunk of hours in the day
for ($hour = 0; $hour -lt 24; $hour += $hoursPerChunk) {
$chunkStartDate = $currentDate.AddHours($hour)
$chunkEndDate = $chunkStartDate.AddHours($hoursPerChunk)
$page = 1
$retryCount = 0
$allMessages = @()
do {
try {
$currentMessages = Get-MessageTrace -StartDate $chunkStartDate -EndDate $chunkEndDate -PageSize $pageSize -Page $page
Write-Host "Fetching emails from Date: $($chunkStartDate.ToString('yyyy-MM-dd HH:mm')) to $($chunkEndDate.ToString('yyyy-MM-dd HH:mm')) Page: $page Msgs Found:" $currentMessages.count -ForegroundColor Cyan
if ($currentMessages.Count -eq 0) {
break
}
$allMessages += $currentMessages
$page++
$retryCount = 0 # Reset retry count on success
# Check if page number exceeds 200
if ($page -gt 200) {
Write-Host "Error: The data set is too large. Please reduce the value of `$hoursPerChunk." -ForegroundColor Red
Exit
}
} catch {
$retryCount++
if ($retryCount -le $maxRetries) {
Write-Host "Error encountered. Retrying in 1 minute... ($retryCount/$maxRetries)" -ForegroundColor Red
Start-Sleep -Seconds 60
} else {
Write-Host "Max retries reached. Exiting..." -ForegroundColor Red
Exit
}
}
} while ($currentMessages.Count -eq $pageSize -or $retryCount -le $maxRetries)
# Filter messages sent to external recipients
$externalMessages = $allMessages | ForEach-Object {
$recipientDomain = $_.RecipientAddress.Split('@')[1]
$senderDomain = $_.SenderAddress.Split('@')[1]
if (-not $authorizedDomains.Contains($recipientDomain) -and $authorizedDomains.Contains($senderDomain)) {
$_
}
}
# Group by sender address and received date (day), and count the number of messages per group
$currentGroupedBySenderAndDate = $externalMessages | Group-Object -Property @{Expression = { "$($_.SenderAddress)-$($_.Received.ToString('yyyy-MM-dd'))" }} |
Select-Object @{Name='Date'; Expression={($_.Group[0].Received).ToString('yyyy-MM-dd')}},
@{Name='Sender'; Expression={($_.Group[0].SenderAddress)}},
Count
# Update the running total
foreach ($group in $currentGroupedBySenderAndDate) {
$existingGroup = $groupedBySenderAndDate | Where-Object { $_.Date -eq $group.Date -and $_.Sender -eq $group.Sender }
if ($existingGroup) {
$existingGroup.Count += $group.Count
} else {
$groupedBySenderAndDate += $group
}
}
# Clear the variable to free up memory
$allMessages = @()
}
}
# Sort the results
$groupedBySenderAndDate = $groupedBySenderAndDate | Sort-Object Count -Descending
# Display the results
$groupedBySenderAndDate | Out-GridView