Search Unified Audit Log for Mailbox Activity
Use two Exchange Online PowerShell scripts to export Unified Audit Log events performed by a mailbox identity or performed to a mailbox object.
Overview
When investigating mailbox activity in Microsoft 365, it helps to separate two related questions:
- What did this mailbox identity do?
- What happened to this mailbox or object?
These two PowerShell scripts search the Unified Audit Log from both directions. Search-UnifiedAuditLog-operations-performed-by.ps1 uses -UserIds to find operations performed by a mailbox identity. Search-UnifiedAuditLog-operations-performed-to.ps1 uses -ObjectIds to find operations performed to or against the mailbox object.
Both scripts connect to Exchange Online, collect large audit result sets in batches, parse the AuditData JSON payload, and export the results to CSV in the same folder as the script. Both scripts accept the mailbox as a required parameter. The performed-by script searches the last seven days by default, and the performed-to script searches the last 90 days by default.
Requirements
- PowerShell on a workstation that can connect to Exchange Online
- The
ExchangeOnlineManagementPowerShell module - A Microsoft 365 account that can run
Search-UnifiedAuditLog - Audit log permissions, typically through the
View-Only Audit LogsorAudit Logsrole - Unified Audit Log retention that covers the date range you want to search
- Local permission to write the exported CSV file
Key Capabilities
- Searches mailbox audit activity by actor with
-UserIds - Searches mailbox audit activity by target object with
-ObjectIds - Accepts the mailbox as a required argument in both scripts
- Uses a unique
SessionIdandReturnLargeSetfor paged audit log retrieval - Requests up to 5,000 records per batch
- Parses
AuditDataJSON into review-friendly CSV columns - Preserves the original raw audit payload in the CSV
- Sorts exported events by time
- Prints result counts after export
- Uses filesystem-safe export names
- Groups remaining operations in the performed-by script after filtering noisy events
Download
Use this script when you want to know what actions were performed by a specific mailbox identity. It searches the Unified Audit Log with -UserIds.
File: Search-UnifiedAuditLog-operations-performed-by.ps1
Use this script when you want to know what actions were performed to or against a specific mailbox or object. It searches the Unified Audit Log with -ObjectIds.
File: Search-UnifiedAuditLog-operations-performed-to.ps1
The Scripts
Operations Performed By
Search-UnifiedAuditLog-operations-performed-by.ps1 is the actor-focused script. It answers, “What did this mailbox identity do?”
The important line is the Search-UnifiedAuditLog call with -UserIds $Mailbox. This version accepts the mailbox identity as a required positional parameter and uses a filesystem-safe mailbox name for the export file. After the events are parsed, the script filters out FileAccessed and MailItemsAccessed so the CSV focuses on less noisy operations. You can add or remove operations in $ExcludedOperations if your investigation needs a different level of detail.
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Mailbox
)
Import-Module ExchangeOnlineManagement -ErrorAction Stop
Connect-ExchangeOnline -ErrorAction Stop
$StartDate = (Get-Date).AddDays(-7)
$EndDate = Get-Date
$SafeMailboxName = $Mailbox -replace '[^a-zA-Z0-9._-]', '_'
$ExportPath = Join-Path $PSScriptRoot "$SafeMailboxName-$($StartDate.ToString('yyyy-MM-dd'))-$($EndDate.ToString('yyyy-MM-dd')).csv"
$SessionId = "PerformedByMailboxAudit_$([guid]::NewGuid().ToString())"
$AllResults = @()
do {
Write-Host "Searching audit log batch... Current count: $($AllResults.Count)"
$Batch = Search-UnifiedAuditLog `
-StartDate $StartDate `
-EndDate $EndDate `
-UserIds $Mailbox `
-SessionId $SessionId `
-SessionCommand ReturnLargeSet `
-ResultSize 5000 `
-ErrorAction Stop
if ($Batch) {
$AllResults += $Batch
}
} while ($Batch.Count -gt 0)
$ParsedResults = $AllResults | ForEach-Object {
$AuditData = $_.AuditData | ConvertFrom-Json
$Parameters = if ($AuditData.Parameters) {
($AuditData.Parameters | ForEach-Object {
"$($_.Name)=$($_.Value)"
}) -join "; "
} else {
""
}
[PSCustomObject]@{
Time = $_.CreationDate
Operation = $_.Operations
Actor = $_.UserIds
Workload = $AuditData.Workload
RecordType = $AuditData.RecordType
ObjectId = $AuditData.ObjectId
MailboxOwnerUPN = $AuditData.MailboxOwnerUPN
MailboxGuid = $AuditData.MailboxGuid
ClientIP = $AuditData.ClientIP
UserAgent = $AuditData.UserAgent
LogonType = $AuditData.LogonType
ResultStatus = $AuditData.ResultStatus
Parameters = $Parameters
RawAuditData = $_.AuditData
}
}
$ExcludedOperations = @(
"FileAccessed",
"MailItemsAccessed"
)
$FilteredResults = $ParsedResults | Where-Object {
$OperationName = "$($_.Operation)".Trim()
$ExcludedOperations -notcontains $OperationName
}
$FilteredResults |
Sort-Object Time |
Export-Csv $ExportPath -NoTypeInformation
Write-Host "Export complete: $ExportPath"
Write-Host "Results found before filtering: $($ParsedResults.Count)"
Write-Host "Results found after filtering: $($FilteredResults.Count)"
Write-Host ""
Write-Host "Remaining operations:"
$FilteredResults |
Group-Object Operation |
Sort-Object Count -Descending |
Select-Object Count, Name |
Format-Table -AutoSize
Operations Performed To
Search-UnifiedAuditLog-operations-performed-to.ps1 is the target-focused script. It answers, “What happened to this mailbox or object?”
The important line is the Search-UnifiedAuditLog call with -ObjectIds $Mailbox. This version accepts the mailbox or object ID as a required positional parameter, searches the last 90 days, and writes a UTF-8 CSV using a filesystem-safe mailbox name. It does not filter operations after parsing, so the exported CSV keeps every returned audit event for the object within the selected date range.
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Mailbox
)
Import-Module ExchangeOnlineManagement -ErrorAction Stop
Connect-ExchangeOnline -ErrorAction Stop
$StartDate = (Get-Date).AddDays(-90)
$EndDate = Get-Date
$SafeMailboxName = $Mailbox -replace '[^a-zA-Z0-9._-]', '_'
$ExportPath = Join-Path $PSScriptRoot "$SafeMailboxName-AllOperationsPerformedTo-Last90Days.csv"
$SessionId = "AuditSearch_$([guid]::NewGuid().ToString())"
$AllResults = @()
do {
Write-Host "Searching audit log batch... Current count: $($AllResults.Count)"
$Batch = Search-UnifiedAuditLog `
-StartDate $StartDate `
-EndDate $EndDate `
-ObjectIds $Mailbox `
-SessionId $SessionId `
-SessionCommand ReturnLargeSet `
-ResultSize 5000 `
-ErrorAction Stop
if ($Batch) {
$AllResults += $Batch
}
} while ($Batch.Count -eq 5000)
$ParsedResults = $AllResults | ForEach-Object {
$AuditData = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
CreationDate = $_.CreationDate
Operation = $_.Operations
UserIds = $_.UserIds
ObjectId = $AuditData.ObjectId
Workload = $_.Workload
ResultStatus = $_.ResultStatus
AuditData = $_.AuditData
}
}
$ParsedResults |
Sort-Object CreationDate |
Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8
Write-Host "Export complete: $ExportPath"
Write-Host "Results found: $($ParsedResults.Count)"
How to Use
- Install the Exchange Online module if needed:
Install-Module ExchangeOnlineManagement
Download the script that matches the question you are asking.
Pass the mailbox identity or object ID when you run the script.
.\Search-UnifiedAuditLog-operations-performed-by.ps1 mailbox@domain.com
or:
.\Search-UnifiedAuditLog-operations-performed-to.ps1 mailbox@domain.com
- Adjust the date range if the default window is not enough. The performed-by script uses seven days by default, and the performed-to script uses 90 days by default:
$StartDate = (Get-Date).AddDays(-30)
$EndDate = Get-Date
- Run the script from PowerShell:
.\Search-UnifiedAuditLog-operations-performed-by.ps1 mailbox@domain.com
or, for the performed-to script:
.\Search-UnifiedAuditLog-operations-performed-to.ps1 mailbox@domain.com
Authenticate to Exchange Online when prompted.
Review the exported CSV in the script folder. The performed-by script generated name uses a filesystem-safe mailbox value and date range:
mailbox_domain.com-2026-05-07-2026-05-14.csv
The performed-to script generated name uses a filesystem-safe mailbox value and a fixed last-90-days suffix:
mailbox_domain.com-AllOperationsPerformedTo-Last90Days.csv
Example Output
The console output for the performed-by script looks like this after the export completes:
Searching audit log batch... Current count: 0
Export complete: C:\Audit\mailbox_domain.com-2026-05-07-2026-05-14.csv
Results found before filtering: 42
Results found after filtering: 8
Remaining operations:
Count Name
----- ----
3 Send
2 UpdateInboxRules
1 MailboxLogin
1 Set-Mailbox
1 HardDelete
The performed-by CSV includes columns like these:
Time,Operation,Actor,Workload,RecordType,ObjectId,MailboxOwnerUPN,MailboxGuid,ClientIP,UserAgent,LogonType,ResultStatus,Parameters,RawAuditData
The performed-to CSV includes columns like these:
CreationDate,Operation,UserIds,ObjectId,Workload,ResultStatus,AuditData
Notes
- Use the performed-by script when the mailbox identity is the actor you care about.
- Use the performed-to script when the mailbox or object is the target you care about.
- The two scripts can return different event sets for the same mailbox because
-UserIdsand-ObjectIdsfilter different audit fields. - The export path is based on
$PSScriptRoot, so the CSV is written next to the script file. Search-UnifiedAuditLogavailability depends on Microsoft 365 audit retention, licensing, permissions, and whether auditing was enabled during the period being investigated.- Large tenants and broad date ranges can return many events. Start with a narrow window, then expand if needed.
MailItemsAccessedandFileAccessedcan be high-volume operations. The performed-by script excludes them by default, but you can remove them from$ExcludedOperationsif you need the full activity set.
Change Log
2026-05-18
- Updated
Search-UnifiedAuditLog-operations-performed-by.ps1to acceptMailboxas a required parameter instead of requiring the script file to be edited. - Added a filesystem-safe export name to the performed-by script while keeping its date-range suffix.
- Updated
Search-UnifiedAuditLog-operations-performed-to.ps1to acceptMailboxas a required parameter instead of requiring the script file to be edited. - Changed the performed-to default search window from seven days to 90 days.
- Added a filesystem-safe export name ending in
AllOperationsPerformedTo-Last90Days.csv. - Simplified the performed-to CSV columns to
CreationDate,Operation,UserIds,ObjectId,Workload,ResultStatus, andAuditData. - Added
-ErrorAction Stopto the performed-toSearch-UnifiedAuditLogcall and exports the CSV with UTF-8 encoding.