# Search-AuditLogGraph.PS1 # Example of how to search the Microsoft 365 unified audit log using the Graph API # In this case, we use the Microsoft Graph PowerShell SDK to do the magic. Tested with SDK V2.15. # See this article for more information: https://practical365.com/audit-log-query-api/ # V1.0 29-Feb-2024 # https://github.com/12Knocksinna/Office365itpros/blob/master/Search-AuditLogGraph.PS1 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") $StartDate = $null; $EndDate = $null; $Operations = $null $StartDate = Read-Host "Start Date for audit search" $EndDate = Read-Host "End Date for audit search" [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.Id If ($null -eq $SearchId) { Write-Host "Search not created" Break } Else { $SearchId = $SearchQuery.Id Write-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 = 20 Write-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 GET While ($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.value Write-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.Service Timestamp = $Record.CreatedDateTime UPN = $Record.userPrincipalName Operation = $Record.operation } $Report.Add($ReportLine) } $Report | Sort-Object {$_.Timestamp -as [datetime]} | Out-GridView -Title 'Audit Records' # An example script used to illustrate a concept. More information about the topic can be found in the Office 365 for IT Pros eBook https://gum.co/O365IT/ # and/or a relevant article on https://office365itpros.com or https://www.practical365.com. See our post about the Office 365 for IT Pros repository # https://office365itpros.com/office-365-github-repository/ for information about the scripts we write. # Do not use our scripts in production until you are satisfied that the code meets the need of your organization. Never run any code downloaded from the Internet without # first validating the code in a non-production environment.