How to Use the Office 365 Audit Log to Find Out Who Deleted Messages in a Mailbox

Auditing Deletions

Updated: March 2021

Ever since Microsoft introduced the current mailbox auditing mechanism in Exchange 2010 (an earlier version in Exchange 2007 took a different approach), it has been used to answer the question of “who deleted that message,” an issue that usually crops up when a delegate removes items from someone else’s mailbox or a shared mailbox and won’t admit their action.

Ingestion and Normalization

Microsoft enables mailbox auditing for all Exchange Online mailboxes for accounts with Office 365 E3 and E5 licenses. Audit records are not generated for accounts with other licenses. The audit records flow through a normalization process before the records are ingested into the Office 365 audit log. Normalization makes sure that the Exchange records have the same format as records from other workloads.

Searching for Email Deletions

You can look for delete operations through the audit log search in the Compliance Center, but it’s usually more convenient (and faster) to use PowerShell and run the Search-UnifiedAuditLog cmdlet.

Here’s an example that searches for hard and soft delete operations and extracts information from the JSON payload which holds the details of the action performed. In this case, we want to find who deleted a message. The results are piped to the Out-GridView cmdlet to view on screen.

$StartDate = (Get-Date).AddDays(-90); $EndDate = (Get-Date) 
[array]$Records = (Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Operations "HardDelete", "SoftDelete" -ResultSize 5000) 
If (!($Records)) { Write-Host "No deletion records found."; break } 
Else { 
 Write-Host "Processing" $Records.Count "audit records..." 
 $Report = [System.Collections.Generic.List[Object]]::new() # Create output file  
 ForEach ($Rec in $Records) { 
    $AuditData = ConvertFrom-Json $Rec.Auditdata 
    If ($AuditData.ResultStatus -eq "PartiallySucceeded") {
        $EMailSubjects = "*** Not deleted by" + $AuditData.ClientInfoString + " ***" }
    Else {
        $EmailSubjects = $AuditData.AffectedItems.Subject -join ", " }
    $ReportLine = [PSCustomObject] @{ 
      TimeStamp          = Get-Date($AuditData.CreationTime) -format g 
      User               = $AuditData.UserId 
      Action             = $AuditData.Operation 
      Status             = $AuditData.ResultStatus 
      Mailbox            = $AuditData.MailboxOwnerUPN 
      "Message Subjects" = $EmailSubjects
      Folder             = $AuditData.Folder.Path.Split("\")[1] 
      Client             = $AuditData.ClientInfoString } 
    $Report.Add($ReportLine) }
  } 
$Report | Sort Mailbox | Select Timestamp, Action, User, Mailbox, "Message Subjects", Folder | Out-GridView

The formatted records are placed in the $Report list. You can slice and dice the records to meet your needs, or export the data to a CSV file and then format it with Excel or Power BI. For example:

$Report | Export-CSV -NoTypeInformation -Path c:\temp\ExchangeMailboxDeletes.csv


If you need to extract the records for a particular user or shared mailbox, apply a filter to the $Report list. For instance, to find just the records for a shared mailbox with a specific primary SMTP address, use a command like this to find the records for a target mailbox and pipe them to the Out-GridView cmdlet.

$Report | ?{$_.Mailbox -eq "BookBuild@office365itpros.com"} | Out-GridView

Figure 1 shows the result.

Filtered records for a specific target mailbox piped to the Out-GridView cmdlet
Figure 1: Filtered records for a specific target mailbox piped to the Out-GridView cmdlet

Another variant on the theme is posted in this article. The script used here is available in the Office 365 for IT Pros GitHub repository.

For more information about the Office 365 audit log and how to configure Exchange mailbox auditing, read Chapter 21 of Office 365 for IT Pros. If you want to read more about reporting from the mailbox audit log rather than the Office 365 audit log, it’s in Chapter 3 of the companion volume.

9 Replies to “How to Use the Office 365 Audit Log to Find Out Who Deleted Messages in a Mailbox”

  1. Hi. This really doesn’t doesn’t indicate who deleted a specific message. It only shows people deleting messages and their subjects. If there are multiple emails with the same subject, how would one determine who deleted that ?

    Example. Target mailbox is a shared mailbox that many users have access to. Litigation Hold is enabled. A user complains that an email they require can no longer be found. This email is a job application and the subject is just the job application number, therefore the mailbox has many items with the same subject. The user supplies the sender of the item.
    A Content Search is performed and the item is found in the Recoverable Items folder. The Content Search Report is saved.
    Your process is performed and shows multiple users deleting items with the required subject. The audit log does not state who the sender of the item was.

    Where/what is the correlation between the audit log and an item found in the mailbox/content search report ?

    Thanks

    1. In your situation, because the audit records don’t include the sender name, you might grab the InternetMessageId and use that to compare against the results of the content search to find the message deleted by an individual. I’ll ask the Exchange team if they can include the sender name in the properties reported in audit events for deleted messages.

      1. 1. Thank you. After add in the parameters -UserIds “xxx@xxx.com” into below line, it work for user’s inbox.

        2. Is this parameters “-UserIds” work for Share mailboxes ? or it have other parameter need to use ?

        $Records = (Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date).AddDays(1) -UserIds “xxx@xxx.com” -Operations “HardDelete”, “SoftDelete” -ResultSize 5000)

      2. UserIds is for the signed in account which performs an action. Shared mailboxes don’t sign in. To locate items specific to a shared mailbox, you’ll need to filter the records to find those for the right mailbox. I updated the post to show how to do this.

  2. I know this is a relatively old post, but it helped me a lot, so thank you!

    Important to note though, for larger amounts of records, this requires the use of session commands. If you don’t, you’ll be capped at 180 returned results no matter what you adjust the -ResultSize switch to be.

    Specific example of where to change things (I also added MoveToDeletedItems):

    [array]$Records = (Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate ***-SessionCommand ReturnLargeSet*** -Operations “HardDelete”, “SoftDelete”, ***”MoveToDeletedItems”*** -ResultSize 5000)

    If you need more than that 5000, you have to define a SessionId and loop the result or repeatedly run the command:

    ***$SessionId = “Whatever you want to name it”***
    [array]$Records = (Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate ***-SessionId $SessionId*** -Operations “HardDelete”, “SoftDelete”, “MoveToDeletedItems”)

    From here: https://learn.microsoft.com/en-us/office/office-365-management-api/aip-unified-audit-logs-best-practices#manage-large-amounts-of-audit-data

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.