Purview/Search-AuditLog.ps1
|
<#
.SYNOPSIS Searches the Microsoft 365 unified audit log. .DESCRIPTION Wraps Search-UnifiedAuditLog with a friendlier interface for common investigation scenarios. Handles pagination automatically and returns parsed audit records. Essential for incident response, compliance investigations, and answering "who did what and when" questions. Requires ExchangeOnlineManagement module and an active Purview/Compliance connection (Connect-IPPSSession). .PARAMETER StartDate Start of the search window. Required. .PARAMETER EndDate End of the search window. Defaults to the current date/time. .PARAMETER UserIds One or more UPNs to filter by. If not specified, searches all users. .PARAMETER Operations One or more audit operations to filter by (e.g., 'FileAccessed', 'MailItemsAccessed', 'UserLoggedIn', 'Add member to role.'). .PARAMETER RecordType Audit record type filter (e.g., 'AzureActiveDirectory', 'ExchangeAdmin', 'SharePointFileOperation'). If not specified, all record types are searched. .PARAMETER OutputPath Optional path to export results as CSV. If not specified, results are returned to the pipeline. .EXAMPLE PS> . .\Common\Connect-Service.ps1 PS> Connect-Service -Service Purview PS> .\Purview\Search-AuditLog.ps1 -StartDate '2026-02-01' -EndDate '2026-03-01' Searches all audit records for the month of February 2026. .EXAMPLE PS> .\Purview\Search-AuditLog.ps1 -StartDate '2026-03-01' -UserIds 'jsmith@contoso.com' -Operations 'FileAccessed','FileDownloaded' Searches for file access activity by a specific user since March 1. .EXAMPLE PS> .\Purview\Search-AuditLog.ps1 -StartDate '2026-02-15' -RecordType 'AzureActiveDirectory' -OutputPath '.\entra-audit.csv' Exports Entra ID audit events to CSV. #> [CmdletBinding()] param( [Parameter(Mandatory)] [datetime]$StartDate, [Parameter()] [datetime]$EndDate = (Get-Date), [Parameter()] [string[]]$UserIds, [Parameter()] [string[]]$Operations, [Parameter()] [string]$RecordType, [Parameter()] [string]$OutputPath ) $ErrorActionPreference = 'Stop' # Verify compliance connection by testing the cmdlet try { $null = Get-Command -Name Search-UnifiedAuditLog -ErrorAction Stop } catch { Write-Error "Search-UnifiedAuditLog is not available. Run Connect-Service -Service Purview first." return } Write-Verbose "Searching audit log from $($StartDate.ToString('yyyy-MM-dd HH:mm')) to $($EndDate.ToString('yyyy-MM-dd HH:mm'))" $searchParams = @{ StartDate = $StartDate EndDate = $EndDate ResultSize = 5000 } if ($UserIds) { $searchParams['UserIds'] = $UserIds Write-Verbose "Filtering by users: $($UserIds -join ', ')" } if ($Operations) { $searchParams['Operations'] = $Operations Write-Verbose "Filtering by operations: $($Operations -join ', ')" } if ($RecordType) { $searchParams['RecordType'] = $RecordType Write-Verbose "Filtering by record type: $RecordType" } # Paginate through results $allRecords = [System.Collections.Generic.List[PSCustomObject]]::new() $sessionId = [guid]::NewGuid().ToString() $searchParams['SessionId'] = $sessionId $searchParams['SessionCommand'] = 'ReturnLargeSet' try { $page = 0 do { $page++ Write-Verbose "Retrieving page $page..." $batch = Search-UnifiedAuditLog @searchParams if ($null -eq $batch -or $batch.Count -eq 0) { break } foreach ($record in $batch) { $auditData = $record.AuditData | ConvertFrom-Json $allRecords.Add([PSCustomObject]@{ CreationDate = $record.CreationDate UserIds = $record.UserIds Operations = $record.Operations RecordType = $record.RecordType ResultIndex = $record.ResultIndex ResultCount = $record.ResultCount ClientIP = $auditData.ClientIP ObjectId = $auditData.ObjectId ItemType = $auditData.ItemType SiteUrl = $auditData.SiteUrl SourceFileName = $auditData.SourceFileName AuditData = $record.AuditData }) } Write-Verbose "Retrieved $($allRecords.Count) of $($batch[0].ResultCount) total records" # Stop if we've retrieved all results if ($allRecords.Count -ge $batch[0].ResultCount) { break } } while ($true) } catch { Write-Error "Audit log search failed: $_" return } Write-Verbose "Total records found: $($allRecords.Count)" if ($OutputPath) { $allRecords | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Output "Exported $($allRecords.Count) audit records to $OutputPath" } else { Write-Output $allRecords } |