Entra / Microsoft 365 · Compliance & audit
Search unified audit log with Graph
Creates and polls a Microsoft Graph audit log query, then exports matching unified audit records for review.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes AuditLogsQuery.Read.All -NoWelcome
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $StartDate = "$null; $EndDate = $null; $Operations = $null",[string] $EndDate = "Read-Host "End Date for audit search")Connect-MgGraph -Scopes AuditLogsQuery.Read.All -NoWelcome$SearchId = $null# Construct basic search parameters# For multiple operations, use "operationFilters" = @("fileaccessed","filedeleted")# For record type filters, use "recordTypeFilters" = @("sharePointFileOperation","threatIntelligence")[array]$UserOperations = Read-Host "Enter the audit operations to search for (separated by commas)"[array]$Operations = $UserOperations.split(",").trim(" ").tolower()Try {$StartDateSearch = (Get-Date $StartDate -format s) + "Z"}Catch {Write-Host ("{0} is not a valid date" -f $StartDate)Break}Try {$EndDateSearch = (Get-Date $EndDate -format s) + "Z"}Catch {Write-Host ("{0} is not a valid date" -f $EndDate)Break}If (!($Operations)) {Write-Host "No audit operations specified - exiting"Break}$SearchName = ("Audit Search created {0}" -f (Get-Date -format 'dd-MMM-yyyy HH:mm'))$SearchParameters = @{"displayName" = $SearchName"filterStartDateTime" = $StartDateSearch"filterEndDateTime" = $EndDateSearch"operationFilters" = $Operations}Write-Host "Creating an audit search query..."$Uri = "https://graph.microsoft.com/beta/security/auditLog/queries"$SearchQuery = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $SearchParameters$SearchId = $SearchQuery.IdIf ($null -eq $SearchId) {Write-Host "Search not created"Break} Else {$SearchId = $SearchQuery.IdWrite-Host ("Audit log search created with id: {0} and name {1}" -f $SearchId, $SearchQuery.displayname)}[int]$i = 1[int]$SleepSeconds = 20$SearchFinished = $false; [int]$SecondsElapsed = 20Write-Host "Checking audit query status..."Start-Sleep -Seconds 20$Uri = ("https://graph.microsoft.com/beta/security/auditLog/queries/{0}" -f $SearchId)$SearchStatus = Invoke-MgGraphRequest -Uri $Uri -Method GETWhile ($SearchFinished -eq $false) {$i++Write-Host ("Waiting for audit search to complete. Check {0} after {1} seconds. Current state {2}" -f $i, $SecondsElapsed, $SearchStatus.status)If ($SearchStatus.status -eq 'succeeded') {$SearchFinished = $true} Else {Start-Sleep -Seconds $SleepSeconds$SecondsElapsed = $SecondsElapsed + $SleepSeconds$SearchStatus = Invoke-MgGraphRequest -Uri $Uri -Method GET}}Write-Host "Fetching audit records found by the search..."$Uri = ("https://graph.microsoft.com/beta/security/auditLog/queries/{0}/records?`$Top=999" -f $SearchId)[array]$SearchRecords = Invoke-MgGraphRequest -Uri $Uri -Method GET[array]$AuditRecords = $SearchRecords.value# Paginate to fetch all available audit records$NextLink = $SearchRecords.'@odata.NextLink'While ($null -ne $NextLink) {$SearchRecords = $null[array]$SearchRecords = Invoke-MgGraphRequest -Uri $NextLink -Method GET$AuditRecords += $SearchRecords.valueWrite-Host ("{0} audit records fetched so far..." -f $AuditRecords.count)$NextLink = $SearchRecords.'@odata.NextLink'}Write-Host ("Total of {0} audit records found" -f $AuditRecords.count) -ForegroundColor Red$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Record in $AuditRecords) {$ReportLine = [PSCustomObject][Ordered]@{Service = $Record.ServiceTimestamp = $Record.CreatedDateTimeUPN = $Record.userPrincipalNameOperation = $Record.operation}$Report.Add($ReportLine)}$Report | Sort-Object {$_.Timestamp -as [datetime]} | Out-GridView -Title 'Audit Records'
Attribution
Author
Office365itpros