Entra / Microsoft 365 · Compliance & audit
Find audit events for a user
Find and report unified audit log events for actions taken by a user over a specified period across Exchange, Teams, SharePoint, and compliance workloads.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-ExchangeOnline -ShowBanner:$False
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $StartDate = "Read-Host "Enter the start date (dd-mm-yyyy)",[string] $EndDate = (Get-Date $StartDate).AddDays(1))[array]$Modules = Get-Module | Select-Object -ExpandProperty NameIf ($Modules -notcontains "ExchangeOnlineManagement") {Write-Host "Connecting to Exchange Online..."Connect-ExchangeOnline -ShowBanner:$False}$User = Read-Host "Enter the user's UPN"If (!(Get-ExoMailbox -Identity $User)) {Write-Host "User $User not found"Exit}Write-Host ("Searching the audit log for actions for {0} between {1} and {2}" -f $User, $StartDate, (Get-Date $EndDate -format 'dd-MMM-yyyy'))[array]$Records = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -UserIds $User -ResultSize 5000 -SessionCommand ReturnLargeSetIf ($Records.Count -eq 0) {Write-Host "No audit records found for $User between $StartDate and $EndDate"Exit}$Records = $Records | Sort-Object Identity -Unique$Records = $Records | Sort-Object {$_.CreationDate -as [datetime]}Write-Host ("Found {0} audit records for {1}. First record from {2}. Last record at {3}" -f $Records.Count, $User, $Records[0].CreationDate, $Records[-1].CreationDate)# Overall summary$RecordsSummary = $Records | Group-Object Operations -NoElement | Sort-Object Count -DescendingWrite-Host "Summary of actions taken by $User"$RecordsSummary | Format-Table Name, Count -AutoSize# Remove UserLoggedIn events[array]$Records1 = $Records | Where-Object {$_.Operations -ne "UserLoggedIn"}$Report = [System.Collections.Generic.List[Object]]::new()Write-Host ("Processing {0} audit records for {1} between {2} and {3}" -f $Records1.Count, $User, $StartDate, $EndDate)# Process each audit record and attempt to make sense of the audit data payloadForEach ($Rec in $Records1) {$Action = $null; $Object = $null; $Location = $null; $Workload = $null$AuditData = $Rec.AuditData | ConvertFrom-JsonSwitch ($Rec.Operations) {"Create" {$Action = 'Item created in mailbox'$Object = $AuditData.Item.Subject$Location = ($AuditData.MailboxOwnerUPN + $AuditData.Item.ParentFolder.Path)$Workload = "Exchange Online"}"CopilotInteraction" {$Action = 'Copilot interaction'$Object = $AuditData.CopilotEventData.ThreadId$Location = $AuditData.CopilotEventData.AppHost$Workload = "Copilot"}"FileAccessed" {$Action = 'File accessed'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"FileModified" {$Action = 'File modified'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"FileRenamed" {$Action = 'File renamed'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"FolderCreated" {$Action = 'Folder created'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"FolderModified" {$Action = 'Folder modified'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"FileModifiedExtended" {$Action = 'File modified'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"FileSensitivityLabelApplied" {$Action = 'Sensitivity label applied to file'$Object = $AuditData.SourceFileName$Location = $AuditData.ObjectId$Workload = "SharePoint Online"}"MailItemsAccessed" {$Action = 'Mail item accessed'If ($AuditData[0].Item.ParentFolder.Name) {$Object = $AuditData[0].Item.ParentFolder.Name} Else {$Object = 'Message Bind'}$Location = ($AuditData[0].MailboxOwnerUPN + $AuditData.Item.ParentFolder.Path)$Workload = "Exchange Online"}"MoveToDeletedItems" {$Action = 'Item moved to Deleted Items'$Object = $Auditdata[0].AffectedItems.Subject$Location = ($AuditData[0].MailboxOwnerUPN + $AuditData[0].AffectedItems.ParentFolder.Path)$Workload = "Exchange Online"}"HardDelete" {$Action = 'Item purged from mailbox'$Object = $Auditdata.AffectedItems[0].Subject$Location = ($AuditData.MailboxOwnerUPN + $AuditData.AffectedItems[0].ParentFolder.Path)$Workload = "Exchange Online"}"SoftDelete" {$Action = 'Item removed to Recoverable Items'$Object = $Auditdata.AffectedItems[0].Subject$Location = ($AuditData.MailboxOwnerUPN + $AuditData.AffectedItems[0].ParentFolder.Path)$Workload = "Exchange Online"}"Send" {$Action = 'Message Sent'$Object = $AuditData[0].Item.Subject$Location = ($AuditData[0].MailboxOwnerUPN + $AuditData[0].Item.ParentFolder.Path)$Workload = "Exchange Online"}"SendAs" {$Action = 'Message sent as another user'$Object = $AuditData[0].Item.Subject$Location = ($AuditData[0].MailboxOwnerUPN + $AuditData[0].Item.ParentFolder.Path)$Workload = "Exchange Online"}"MessageSent" {$Action = 'Teams message sent'$Object = $AuditData.MessageId$Location = ("Posted to {0} in {1}" -f $AuditData.ChannelName, $AuditData.TeamName)$Workload = "Microsoft Teams"}"MessageCreatedHasLink" {$Action = 'Teams message created with link'$Object = $AuditData.MessageId$Location = ("Posted to {0} in {1}" -f $AuditData.ChannelName, $AuditData.TeamName)$Workload = "Microsoft Teams"}"TaskCreated" {$Action = 'Task created'$Object = $AuditData.ObjectId$Location = $AuditData.Workload$Workload = "Planner"}"TaskModified" {$Action = 'Task modified'$Object = $AuditData.ObjectId$Location = $AuditData.Workload$Workload = "Planner"}"TaskRead" {$Action = 'Task read'$Object = $AuditData.ObjectId$Location = $AuditData.Workload$Workload = "Planner"}"TaskListRead" {$Action = 'Task list read'$Object = $AuditData.ObjectId$Location = $AuditData.Workload$Workload = "Planner"}"PlanRead" {$Action = 'Plan read'$Object = $AuditData.ObjectId$Location = $AuditData.Workload$Workload = "Planner"}"NewRetentionCompliancePolicy" {$Action = 'Retention policy created'$Object = $Auditdata.Parameters.Value.Split("-A")[0].Split('"')[1]$Location = $AuditData.Workload$Workload = "Data lifecycle management"}"NewRetentionComplianceRule" {$Action = 'Retention rule created'$Object = $Auditdata.Parameters.value[0].Split("-Expiration")[1]$Location = $Auditdata.Parameters.Value.Split("-A")[0].Split('"')[1]$Workload = "Data lifecycle management"}"Get-ComplianceSearch" {$Action = 'Compliance search retrieved'If ($AuditData.Parameters -eq '-ResultSize "Unlimited"' ) {$Object = "All results"} Else {Try {$Object = $AuditData.Parameters.Split('"')[1]} Catch {$Object = "Unknown"}}$Location = $AuditData.Workload$Workload = 'eDiscovery'}"Set-ComplianceSearch" {$Action = 'Compliance search updated'$Object = $Auditdata.Parameters.Split("-D")[0].Split('"')[1]$Location = $AuditData.Workload$Workload = 'eDiscovery'}"Start-ComplianceSearch" {$Action = 'Compliance search started'$Object = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String( $AuditData.Parameters.Split('"')[1].SubString(0,32)))$Location = $AuditData.Workload$Workload = 'eDiscovery'}"New-ComplianceSearchAction" {$Action = 'Compliance search action created'$Object = $AuditData.Parameters$Location = $AuditData.Workload$Workload = 'eDiscovery'}"Get-ComplianceCase" {$Action = 'Compliance case retrieved'$Object = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String( $AuditData.Parameters.Split('"')[1].SubString(0,48)))$Location = $AuditData.Workload$Workload = "eDiscovery"}"Get-ComplianceCaseMember" {$Action = 'Compliance case membership retrieved'$Object = $AuditData.Parameters$Location = $AuditData.Workload$Workload = "eDiscovery"}"Set-Mailbox" {$Action = 'Mailbox settings updated'$Object = $AuditData.Parameters.value[0]$Location = $AuditData.Workload$Workload = 'Exchange Online PowerShell'}"Search" {$Action = 'Search performed'$Object = $AuditData.DataType$Location = $AuditData.DatabaseType$Workload = "Data Insights"}"SearchQueryInitiatedSharePoint" {$Action = 'SharePoint Search performed'$Object = $AuditData.QueryText$Location = $AuditData.ScenarioName$Workload = "SharePoint Online"}"SearchQueryInitiatedExchange" {$Action = 'Exchange Search performed'$Object = $AuditData.QueryText$Location = $AuditData.ScenarioName$Workload = "Exchange Online"}"SearchViewed" {$Action = 'Search results viewed'$Object = $AuditData.ObjectId$Location = $AuditData.Query$Workload = "eDiscovery"}"QueryUpdate" {$Action = 'Search query updated'$Object = $AuditData.QueryText$Location = $AuditData.CaseId$Workload = "eDiscovery"}"Validate" {$Action = 'Search query validated'$Object = $AuditData.DataType$Location = $AuditData.RelativeURL$Workload = "eDiscovery"}"GATFRTokenIssue" {$Action = 'Get Access Token for Resource'$Object = $AuditData.ResourceURL$Location = "Outlook Web Access"$Workload = "Exchange Online"}Default {$Action = $Rec.Operations$Object = $AuditData.ObjectId$Location = $AuditData.Workload$Workload = $AuditData.Workload}}$ReportLine = [pscustomobject]@{CreationDate = $Rec.CreationDateOperation = $Rec.OperationsUser = $Rec.UserIdsRecord = $Rec.RecordTypeAction = $ActionObject = $ObjectLocation = $LocationWorkload = $Workload}$Report.Add($ReportLine)}# Show what we've done...$Report | Out-GridView -Title "Audit events for $User between $StartDate and $EndDate"
Parameters
ParameterDefaultNotes
-StartDateRead-Host "Enter the start date (dd-mm-yyyy)"Start date for the audit log search (dd-mm-yyyy); prompted interactively if omitted.-EndDate(Get-Date $StartDate).AddDays(1)End of the audit log search window.Attribution
Author
Office365itpros