Entra / Microsoft 365 · Exchange Online
Analyze mail traffic users
Using the Exchange Online message trace log to analyze messages sent to external and internal domains by mailboxes.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-ExchangeOnline
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([int] $LookbackDays = 10,[string] $StartDate = (Get-Date).AddDays(-$LookbackDays),[string] $EndDate = (Get-Date),[int] $BatchSize = 2000)If ($Null -eq (Get-ConnectionInformation)) {Connect-ExchangeOnline}[array]$Messages = $Null# Message trace date is kept for a maximum of 10 daysWrite-Host ("Message trace data will be analyzed between {0} and {1}" -f $StartDate, $EndDate)[array]$Messages = $Null# Define the page size to fetch messages.[int]$BatchSizeForMessages = 1000# original code [array]$MessagePage = Get-MessageTrace -StartDate $StartDate -EndDate $EndDate -PageSize 1000 -Page $i -Status "Delivered"Try {# The warning action is suppressed here because we don't want to see warnings when more data is available[array]$MessagePage = Get-MessageTraceV2 -StartDate $StartDate -EndDate $EndDate `-ResultSize $BatchSizeForMessages -Status "Delivered" -ErrorAction Stop -WarningAction SilentlyContinue$Messages += $MessagePage# Very basic throttling in an attempt to avoid Exchange Online not being able to return pages of message trace data.Start-Sleep -Milliseconds 250} Catch {Write-Host ("Error fetching message trace data: {0}" -f $_.Exception.Message)Break}If ($MessagePage.count -eq $BatchSizeForMessages) {Do {Write-Host ("Fetched {0} messages so far" -f $Messages.count)$LastMessageFetched = $MessagePage[-1]$LastMessageFetchedDate = $LastMessageFetched.Received.ToString("O")$LastMessageFetchedRecipient = $LastMessageFetched.RecipientAddress# Fetch the next page of messages[array]$MessagePage = Get-MessageTraceV2 -StartDate $StartDate -EndDate $LastMessageFetchedDate `-StartingRecipientAddress $LastMessageFetchedRecipient -ResultSize $BatchSizeForMessages -Status "Delivered" -ErrorAction Stop -WarningAction SilentlyContinueIf ($MessagePage) {$Messages += $MessagePage}} While ($MessagePage.count -eq $BatchSizeForMessages)}# Remove Exchange Online public folder hierarchy synchronization messages$Messages = $Messages | Where-Object {$_.Subject -NotLike "*HierarchySync*"}# Get a list of vertified domains for the tenant so we can differentiate between internal# and external email based on recipient address[array]$Domains = Get-AcceptedDomain | Select-Object -ExpandProperty DomainName# Fetch a list of user and shared mailboxes to process[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox, SharedMailbox -ResultSize Unlimited | Sort-Object DisplayName$MessageReport = [System.Collections.Generic.List[Object]]::new()ForEach ($User in $Mbx) {Write-Host ("Processing email for {0}" -f $User.DisplayName)# Get messages sent by the user[array]$UserMessages = $Messages | Where-Object {$_.SenderAddress -eq $User.PrimarySmtpAddress}If ($UserMessages) {# We’ve found some messages to process, so let’s do that[int]$ExternalEmail = 0; [int]$InternalEmail = 0; [array]$ExternalDomains = $NullForEach ($M in $UserMessages) {$MsgRecipientDomain = $M.RecipientAddress.Split('@')[1]If ($MsgRecipientDomain -in $Domains) {$InternalEmail++} Else {$ExternalEmail++$ExternalDomains += $MsgRecipientDomain}} # End Foreach message$ExternalDomains = $ExternalDomains | Sort-Object -Unique$PercentInternal = "N/A"; $PercentExternal = "N/A"If ($InternalEmail -gt 0) {$PercentInternal = ($InternalEmail/($UserMessages.count)).toString("P") }If ($ExternalEmail -gt 0) {$PercentExternal = ($ExternalEmail/($UserMessages.count)).toString("P") }Switch ($User.RecipientTypeDetails) {"UserMailbox" { $Type = "User"}"SharedMailbox" { $Type = "Shared"}}$ReportLine = [PSCustomObject]@{User = $User.UserPrincipalNameName = ("{0} ({1})" -f $User.DisplayName, $Type)Internal = $InternalEmail"% Internal" = $PercentInternalExternal = $ExternalEmail"% External" = $PercentExternal"External Domains" = $ExternalDomains -Join ", "}$MessageReport.Add($ReportLine)} # End if user (has some messages)} # End ForEach mailboxes# Generate a report$ReportFile = "c:\temp\UserMailTraffic.html"$CSVFile = "c:\temp\UserMailTraffic.csv"$HtmlHead="<html><style>BODY{font-family: Arial; font-size: 8pt;}H1{font-size: 22px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H2{font-size: 18px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H3{font-size: 16px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;}TD{border: 1px solid #969595; padding: 5px; }td.pass{background: #B7EB83;}td.warn{background: #FFF275;}td.fail{background: #FF2626; color: #ffffff;}td.info{background: #85D4FF;}</style><body><div align=center><p><h1>Message Traffic User Analysis</h1></p><p><h3>Generated: " + (Get-Date -format 'dd-MMM-yyyy hh:mm') + " for " + (Get-OrganizationConfig | Select-Object -ExpandProperty DisplayName) + "</h3></p></div>"$HtmlBody = $MessageReport | ConvertTo-Html -Fragment"</body></html><p>" + $HtmlHead + $Htmlbody + "<p>" | Out-File $ReportFile -Encoding UTF8$MessageReport | Export-Csv -NoTypeInformation $CSVFileWrite-Host ("All done - the HTML report is available in {0} and CSV in {1}" -f $ReportFile, $CSVFile)
Parameters
ParameterDefaultNotes
-LookbackDays10Number of days to include in the message trace search window.-StartDate(Get-Date).AddDays(-10)Start of the reporting window.-EndDate(Get-Date)End of the reporting window.-BatchSize2000Maximum number of records to fetch per query batch.Attribution
Author
Office365itpros