5 minutes reading time (1000 words)

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 PowerShell
Connect-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 

Related Posts

 

Comments

No comments made yet. Be the first to submit a comment
Already Registered? Login Here
Thursday, 01 June 2023
You can help support this website by buying me a coffee!
Buy Me A Coffee