Entra / Microsoft 365 · Compliance & audit
Find MailItemsAccessed audit records
Process Microsoft 365 MailItemsAccessed unified audit log records and format them into a more digestible report.
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)Clear-Host$Now = Get-DateWrite-Host "Finding MailItemsAccessed records..."$SearchStartDate = (Get-Date).AddDays(-$LookbackDays) #For Message Trace$SearchEndDate = (Get-Date).AddDays(+1)# Edit this line to use whatever search terms you wnat to find audit records[array]$Records = (Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-$LookbackDays) -EndDate $SearchEndDate -Operations MailItemsAccessed -SessionCommand ReturnLargeSet -ResultSize 5000)If ($Records.Count -eq 0) {Write-Host "No audit records for mail access found."Break}Clear-Host# Remove any duplicates$Records = $Records | Sort-Object Identity -Unique$ProgressDelta = 100/($Records.count); $PercentComplete = 0; $RecordNumber = 0;Write-Host "Processing" $Records.Count "MailItemsAccessed audit records..."$Report = [System.Collections.Generic.List[Object]]::new() # Create output file# Scan each audit record to extract informationForEach ($Rec in $Records) {$RecordNumber++$AuditData = ConvertFrom-Json $Rec.Auditdata$TimeStamp = Get-Date($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm:ss'Write-Output ("Checking audit record {0}" -f $RecordNumber)Switch ($AuditData.LogonType) { #Easier to read than to remember logon type codes"0" {$LogonType = "User"}"1" {$LogonType = "Admin"}"2" {$LogonType = "Delegate"}} # End Switch$RecordType = $Auditdata.OperationProperties | Where-Object {$_.Name -eq "MailAccessType"}$DaysSince = New-TimeSpan(Get-Date($AuditData.CreationTime))$i = 0Switch ($RecordType.Value) { #Figure out the folder names and extract message information for Bind events"Bind" { $FolderId = $AuditData.Folders.Id; $Folder = $AuditData.Folders.Path.Split("\")[1]ForEach ($Msg in $Auditdata.Folders.FolderItems) {$i++; $Subject = $Null# Section to try and find the message subject using a message trace$MsgId = $Msg.InternetMessageId.Substring(1, ($Msg.InternetMessageId.Length -2)) #Trim the message identifierIf ($DaysSince.Days -le 10) { # Within the last 10 days so try a message trace$Subject = (Get-MessageTrace -MessageId $MsgId -StartDate $SearchStartDate -EndDate $SearchEndDate)If ($Null -ne $Subject) {$Subject = $Subject[0].Subject } #If we get a subject, take the first because a trace might return multiple results} # Not worth tracing because it's too far backElse{ $Subject = "Too far back to trace" } # Find message subjectIf ($Subject -eq $Null) { $Subject = "***** No trace data available ******" } # catch allIf ($i -eq 1) { $Messages = "(" + $i + ") " + $MsgId + " (" + $Subject + ")" } # Format outputElse { $Messages = $Messages + "; (" + $i + ") " + $MsgId + " (" + $Subject + ")" }} #End Foreach} #End of Bind-specific processing"Sync" { $FolderId = $AuditData.Item.Id; $Folder = $AuditData.Item.Parentfolder.Name$Messages = $Null }} #End Switch$Throttled = $Null$Throttled = $Auditdata.OperationProperties |Where-Object {$_.Name -eq "IsThrottled"}$ReportLine = [PSCustomObject] @{TimeStamp = $TimeStampMailbox = $AuditData.MailboxOwnerUPNUser = $AuditData.UserIdLogonType = $LogonTypeFolderId = $FolderIdFolder = $FolderAccess = $RecordType.ValueOperation = $RecordType.NameThrottled = $Throttled.ValueClientIP = $AuditData.ClientIPAddressClientInfo = $AuditData.ClientInfoStringSessionId = $AuditData.SessionIdOperations = $AuditData.OperationCountMessages = $Messages }$Report.Add($ReportLine)}$Report | Sort-Object {$_.TimeStamp -as [DateTime]} | Out-GridView$Report | Sort-Object {$_.TimeStamp -as [DateTime]} | Export-CSV -NoTypeInformation c:\temp\MailItemsAccessed.csv
Parameters
ParameterDefaultNotes
-LookbackDays10Number of days back to search MailItemsAccessed audit records.Attribution
Author
Office365itpros