Table of Contents
It’s Just Different in the Cloud
One of the interesting things about Office 365 is the way that the integrated nature of the suite forces people to rethink how they previously approached tasks. Take for instance the question that Exchange administrators have been asked to answer for years: “who sent that email”? A question that takes us to Exchange Send As audit records.
The Development of Exchange Mailbox Auditing
When Exchange 2010 SP1 first introduced mailbox auditing, Microsoft gave administrators the tools to answer the question. The audit reports were clunky (horrible), but you could get the job done in PowerShell by running the Search-MailboxAuditLog cmdlet to search the audit records gathered for delegate activity to find the SendAs events generated when someone used that permission to send a message for another mailbox. That is, if you had enabled auditing for the mailboxes.
It took Microsoft far too long to enable auditing for all mailboxes by default. This was announced for Exchange Online in mid-2018 and eventually enabled in early 2019. However, as explained in this post, if you have Office 365 E3 licenses, you still need to manually enable mailbox auditing for mailboxes assigned those licenses.
Searching for Exchange Send As Audit Records
To return to the famous question, tenants can use the Search-UnifiedAuditLog cmdlet to search the audit log for SendAs events and produce an answer. And that’s exactly what you see in many of the scripts offered as the basis for answering our question, including an earlier post of mine.
Adjustments Needed for Office 365
However neat such an answer is, time has moved on and the answers given previously are now wrong. Unlike Exchange on-premises, where all SendAs events are generated by delegates sending messages for another mailbox, Exchange Online processes messages sent by other workloads that can show up in audit searches and skew results unless adjustments are applied. Here are some things to consider:
- Audit records with S-1-5-18 captured in the UserId property record the generation of a welcome message for a new team.
- Audit records are generated when Teams sends a welcome message.
- Audit records are generated for the group mailbox when a member posts a message to a conversation in an Outlook group using OWA. Records are not generated when messages are posted with other clients or arrive from guest members.
- Audit records are generated for the group mailbox when someone updates a task in Planner.
A Script to Find SendAs Audit Records
With these caveats in mind, here’s a script to search for SendAs records and process SendAs audit records and identify those belonging to user/shared mailboxes and those belonging to group mailboxes. The former category is normally what people are concerned with because they’re looking for instances where someone sent a message from a mailbox rather than posting to an Outlook group or adding a comment with Planner. To help identify the two categories, we create a hash table of primary email addresses for mailboxes and groups and look that table up for each audit event.
# First populate the Recipients Hash Table with user mailboxes, group mailboxes, and shared mailboxes CLS Write-Host "Populating Recipients Table..." $RecipientsTable = @{} Try { $Recipients = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox} Catch { Write-Host "Can't find recipients" ; break} # Now Populate hash table with label data $Recipients.ForEach( { $RecipientsTable.Add([String]$_.PrimarySmtpAddress, $_.RecipientTypeDetails) } ) # And include group mailboxes [array]$GroupMailboxes = Get-Mailbox -ResultSize Unlimited -GroupMailbox $GroupMailboxes.ForEach( { $RecipientsTable.Add([String]$_.PrimarySmtpAddress, $_.RecipientTypeDetails) } ) Write-Host "Finding audit records for Send As operations..." $Records = (Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date).AddDays(+1) -Operations "SendAs" -ResultSize 1000) If ($Records.Count -eq 0) { Write-Host "No audit records for Send As found." } Else { Write-Host "Processing" $Records.Count "Send As audit records..." $Report = [System.Collections.Generic.List[Object]]::new() # Create output file # Scan each audit record to extract information ForEach ($Rec in $Records) { $AuditData = ConvertFrom-Json $Rec.Auditdata $MailboxType = $RecipientsTable.Item($AuditData.MailboxOwnerUPN) # Look up hash table If ($MailboxType -eq "GroupMailbox") {$Reason = "Group Mailbox Send"} Else {$Reason = "Delegate Send As"} If ($AuditData.UserId -eq "S-1-5-18") {$UserId = "Service Account"} Else {$UserId = $AuditData.UserId} $ReportLine = [PSCustomObject] @{ TimeStamp = Get-Date($AuditData.CreationTime) -format g SentBy = $AuditData.MailboxOwnerUPN SentAs = $AuditData.SendAsUserSmtp Subject = $AuditData.Item.Subject User = $AuditData.UserId Action = $AuditData.Operation Reason = $Reason UserType = $AuditData.UserType LogonType = $AuditData.LogonType ClientIP = $AuditData.ClientIP MailboxType = $MailboxType ClientInfo = $AuditData.ClientInfoString Status = $AuditData.ResultStatus } $Report.Add($ReportLine) } } $Report | ? {$_.MailboxType -eq "UserMailbox"} | Out-GridView $Report |Export-Csv -NoTypeInformation -Path c:\temp\SendASAuditRecords.csv Write-Host "Report File saved in" c:\temp\SendASAuditRecords.csv
Figure 1 shows an example of some audit records found for user and shared mailboxes as generated by the script. The complete set of processed records is written out to a CSV file where the data can be parsed and analyzed to your heart’s content.

The script is available for download in GitHub.
The Influence of the Substrate
The problem with taking on-premises solutions like scripts to the cloud is that the solutions often don’t accommodate the way applications work and integrate. The influence of the Office 365 substrate is felt across the entire suite and is growing, which is why it’s wise to keep an eye on how scripts work as Microsoft introduces new functionality.
Chapter 21 of the Office 365 for IT Pros eBook includes many examples of how to interrogate the audit log to retrieve useful information. It’s surprising just how much you can discover if you go looking.
Thanks, Tony. Useful script.