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.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.
Connect-ExchangeOnline -UserPrincipalName admin@yourdomain.com
Get-AdminAuditLogConfig | FL UnifiedAuditLogIngestionEnabled
Enable-OrganizationCustomization
Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true
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.
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
# 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.
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