Search the Office 365 (Azure AD) audit log using PowerShell Search-UnifiedAuditLog and export to CSV
How to search the Office 365 (Azure AD) audit log using PowerShell Search-UnifiedAuditLog and export to CSV file.
The Azure portal only allows searching the audit log for the last month. If you want to search older events, you'll need to use the Exchange Online Search-UnifiedAuditLog PowerShell command.
In this example we are searching the audit log to find out who created an Office 365 user account.Before you begin
If audit logging is not enabled, you'll need to first enable organization customization and then enable audit log search using the following PowerShell commands.
1. Connect to Exchange Online
Connect-ExchangeOnline -UserPrincipalName admin@yourdomain.com
2. Check if audit logging is enabled using PowerShell
Get-AdminAuditLogConfig | FL UnifiedAuditLogIngestionEnabled
3. Enable organization customization using PowerShell
Enable-OrganizationCustomization
4. Enable audit logging using PowerShell
Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true
Enable-OrganizationCustomization error "Organization is already enabled for customization"
If you get this error when running Enable-OrganizationCustomization, it's because you haven't waited long enough after running the PowerShell command.
Write-ErrorMessage : |System.InvalidOperationException|This operation is not required. Organization is already enabled for customization.
Search Office 365 audit log using PowerShell Search-UnifiedAuditLog and export to CSV
The following PowerShell script is from this Microsoft article with some minor modifications. Update the variables at the beginning of the script with your search criteria
Use a PowerShell script to search the audit log
https://learn.microsoft.com/en-us/microsoft-365/compliance/audit-log-search-script
Example: Search the audit log for Azure Active Directory events in the last two days and export to CSV
# Connect to Exchange Online PowerShellConnect-ExchangeOnline -UserPrincipalName admin@yourdomain.com
#SearchAuditLog.ps1 #Modify the values for the following variables to configure the audit log search. $logFile = "c:\temp\AuditLogSearchLog.txt" $outputFile = "c:\temp\AuditLogRecords.csv" [DateTime]$start = [DateTime]::UtcNow.AddDays(-2) [DateTime]$end = [DateTime]::UtcNow $record = "AzureActiveDirectory" $resultSize = 5000 $intervalMinutes = 60 #Start script [DateTime]$currentStart = $start [DateTime]$currentEnd = $start Function Write-LogFile ([String]$Message) { $final = [DateTime]::Now.ToUniversalTime().ToString("s") + ":" + $Message $final | Out-File $logFile -Append } Write-LogFile "BEGIN: Retrieving audit records between $($start) and $($end), RecordType=$record, PageSize=$resultSize." Write-Host "Retrieving audit records for the date range between $($start) and $($end), RecordType=$record, ResultsSize=$resultSize" $totalCount = 0 while ($true) { $currentEnd = $currentStart.AddMinutes($intervalMinutes) if ($currentEnd -gt $end) { $currentEnd = $end } if ($currentStart -eq $currentEnd) { break } $sessionID = [Guid]::NewGuid().ToString() + "_" + "ExtractLogs" + (Get-Date).ToString("yyyyMMddHHmmssfff") Write-LogFile "INFO: Retrieving audit records for activities performed between $($currentStart) and $($currentEnd)" Write-Host "Retrieving audit records for activities performed between $($currentStart) and $($currentEnd)" $currentCount = 0 $sw = [Diagnostics.StopWatch]::StartNew() do { $results = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record -SessionId $sessionID -SessionCommand ReturnLargeSet -ResultSize $resultSize if (($results | Measure-Object).Count -ne 0) { $results | export-csv -Path $outputFile -Append -NoTypeInformation $currentTotal = $results[0].ResultCount $totalCount += $results.Count $currentCount += $results.Count Write-LogFile "INFO: Retrieved $($currentCount) audit records out of the total $($currentTotal)" if ($currentTotal -eq $results[$results.Count - 1].ResultIndex) { $message = "INFO: Successfully retrieved $($currentTotal) audit records for the current time range. Moving on!" Write-LogFile $message Write-Host "Successfully retrieved $($currentTotal) audit records for the current time range. Moving on to the next interval." -foregroundColor Yellow "" break } } } while (($results | Measure-Object).Count -ne 0) $currentStart = $currentEnd } Write-LogFile "END: Retrieving audit records between $($start) and $($end), RecordType=$record, PageSize=$resultSize, total count: $totalCount." Write-Host "Script complete! Finished retrieving audit records for the date range between $($start) and $($end). Total count: $totalCount" -foregroundColor Green
In the results, we can see that the "Add user" operation was performed by itadmin. The AuditData field contains the details of the user account that was created - user account name, assigned license etc.
Example: Search the audit log for Azure Active Directory "Add user" events by date and export to CSV
This example is a more specific search looking for Azure Active Directory "Add user" operations within a date range.
Start date: mm/dd/yyyy
End date: mm/dd/yyyy
Record type: AzureActiveDirectory
Operation: Add user
#SearchAuditLog.ps1 #Modify the values for the following variables to configure the audit log search. $logFile = "c:\temp\AuditLogSearchLog.txt" $outputFile = "c:\temp\AuditLogRecords.csv" $start = "06/01/2022" $end = "06/30/2022" $record = "AzureActiveDirectory" $operation = "Add user" $resultSize = 5000 $intervalMinutes = 60 #Start script [DateTime]$currentStart = $start [DateTime]$currentEnd = $start Function Write-LogFile ([String]$Message) { $final = [DateTime]::Now.ToUniversalTime().ToString("s") + ":" + $Message $final | Out-File $logFile -Append } Write-LogFile "BEGIN: Retrieving audit records between $($start) and $($end), RecordType=$record, PageSize=$resultSize." Write-Host "Retrieving audit records for the date range between $($start) and $($end), RecordType=$record, ResultsSize=$resultSize" $totalCount = 0 while ($true) { $currentEnd = $currentStart.AddMinutes($intervalMinutes) if ($currentEnd -gt $end) { $currentEnd = $end } if ($currentStart -eq $currentEnd) { break } $sessionID = [Guid]::NewGuid().ToString() + "_" + "ExtractLogs" + (Get-Date).ToString("yyyyMMddHHmmssfff") Write-LogFile "INFO: Retrieving audit records for activities performed between $($currentStart) and $($currentEnd)" Write-Host "Retrieving audit records for activities performed between $($currentStart) and $($currentEnd)" $currentCount = 0 $sw = [Diagnostics.StopWatch]::StartNew() do { $results = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -Operations $operation -RecordType $record -SessionId $sessionID -SessionCommand ReturnLargeSet -ResultSize $resultSize if (($results | Measure-Object).Count -ne 0) { $results | export-csv -Path $outputFile -Append -NoTypeInformation $currentTotal = $results[0].ResultCount $totalCount += $results.Count $currentCount += $results.Count Write-LogFile "INFO: Retrieved $($currentCount) audit records out of the total $($currentTotal)" if ($currentTotal -eq $results[$results.Count - 1].ResultIndex) { $message = "INFO: Successfully retrieved $($currentTotal) audit records for the current time range. Moving on!" Write-LogFile $message Write-Host "Successfully retrieved $($currentTotal) audit records for the current time range. Moving on to the next interval." -foregroundColor Yellow "" break } } } while (($results | Measure-Object).Count -ne 0) $currentStart = $currentEnd } Write-LogFile "END: Retrieving audit records between $($start) and $($end), RecordType=$record, PageSize=$resultSize, total count: $totalCount." Write-Host "Script complete! Finished retrieving audit records for the date range between $($start) and $($end). Total count: $totalCount" -foregroundColor Green
Comments