Scripts/Get-UAL.ps1
# This contains functions for getting the unified audit log entries $resultSize = 5000 function Get-UALAll { <# .SYNOPSIS Gets all the unified audit log entries. .DESCRIPTION Makes it possible to extract all unified audit data out of a Microsoft 365 environment. The output will be written to: Output\UnifiedAuditLog\ .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .PARAMETER StartDate startDate is the parameter specifying the start date of the date range. Default: Today -90 days .PARAMETER EndDate endDate is the parameter specifying the end date of the date range. Default: Now .PARAMETER Interval Interval is the parameter specifying the interval in which the logs are being gathered. Default: 720 minutes .PARAMETER Output Output is the parameter specifying the CSV or JSON output type. Default: CSV .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\UnifiedAuditLog .PARAMETER MergeOutput MergeOutput is the parameter specifying if you wish to merge CSV outputs to a single file. .PARAMETER Encoding Encoding is the parameter specifying the encoding of the CSV/JSON output file. Default: UTF8 .PARAMETER ObjecIDs The ObjectIds parameter filters the log entries by object ID. The object ID is the target object that was acted upon, and depends on the RecordType and Operations values of the event. You can enter multiple values separated by commas. .EXAMPLE Get-UALAll Gets all the unified audit log entries. .EXAMPLE Get-UALAll -UserIds Test@invictus-ir.com Gets all the unified audit log entries for the user Test@invictus-ir.com. .EXAMPLE Get-UALAll -UserIds "Test@invictus-ir.com,HR@invictus-ir.com" Gets all the unified audit log entries for the users Test@invictus-ir.com and HR@invictus-ir.com. .EXAMPLE Get-UALAll -UserIds Test@invictus-ir.com -StartDate 1/4/2023 -EndDate 5/4/2023 Gets all the unified audit log entries between 1/4/2023 and 5/4/2023 for the user Test@invictus-ir.com. .EXAMPLE Get-UALAll -UserIds -Interval 720 Gets all the unified audit log entries with a time interval of 720. .EXAMPLE Get-UALAll -UserIds Test@invictus-ir.com -MergeOutput Gets all the unified audit log entries for the user Test@invictus-ir.com and adds a combined output JSON file at the end of acquisition .EXAMPLE Get-UALAll -UserIds Test@invictus-ir.com -Output JSON Gets all the unified audit log entries for the user Test@invictus-ir.com in JSON format. #> [CmdletBinding()] param ( [string]$StartDate, [string]$EndDate, [string]$UserIds = "*", [int]$Interval = 720, [string]$Output = "CSV", [switch]$MergeOutput, [string]$OutputDir, [string]$Encoding = "UTF8", [string]$ObjectIds ) try { $areYouConnected = Get-AdminAuditLogConfig -ErrorAction stop } catch { write-logFile -Message "[INFO] Ensure you are connected to M365 by running the Connect-M365 command before executing this script" -Color "Yellow" Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" throw } write-logFile -Message "[INFO] Running Get-UALAll" -Color "Green" StartDate EndDate $date = [datetime]::Now.ToString('yyyyMMddHHmmss') if ($OutputDir -eq "" ){ $OutputDir = "Output\UnifiedAuditLog\$date" If (!(test-path $OutputDir)) { Write-LogFile -Message "[INFO] Creating the following directory: $OutputDir" New-Item -ItemType Directory -Force -Name $OutputDir > $null } } else { if (Test-Path -Path $OutputDir) { write-LogFile -Message "[INFO] Custom directory set to: $OutputDir" } else { write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" } } $resetInterval = $Interval [DateTime]$currentStart = $script:StartDate [DateTime]$currentEnd = $script:EndDate Write-LogFile -Message "[INFO] Extracting all available audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Green" $baseSearchQuery = @{ UserIds = $UserIds } if ($ObjectIds) { $baseSearchQuery.ObjectIds = $ObjectIds Write-LogFile -Message "[INFO] Filtering by ObjectIds: $ObjectIds" -Color "Green" } while ($currentStart -lt $script:EndDate) { $currentEnd = $currentStart.AddMinutes($Interval) $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd @baseSearchQuery -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($null -eq $amountResults) { Write-LogFile -Message "[INFO] No audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")). Moving on!" $CurrentStart = $CurrentEnd } elseif ($amountResults -gt 5000) { while ($amountResults -gt 5000) { $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $CurrentEnd -ResultSize 1 @baseSearchQuery | Select-Object -First 1 -ExpandProperty ResultCount if ($amountResults -lt 5000) { if ($Interval -eq 0) { Exit } } else { Write-LogFile -Message "[WARNING] $amountResults entries between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) exceeding the maximum of 5000 of entries" -Color "Red" $interval = [math]::Round(($Interval/(($amountResults/5000)*1.25)),2) $currentEnd = $currentStart.AddMinutes($Interval) Write-LogFile -Message "[INFO] Temporary lowering time interval to $Interval minutes" -Color "Yellow" } } } else { $Interval = $resetInterval if ($currentEnd -gt $script:EndDate) { $currentEnd = $script:EndDate } $currentTries = 0 $sessionID = $currentStart.ToString("yyyyMMddHHmmss") while ($true) { [Array]$results = Search-UnifiedAuditLog -StartDate $CurrentStart -EndDate $currentEnd -SessionCommand ReturnLargeSet -ResultSize $resultSize @baseSearchQuery $currentCount = 0 if ($null -eq $results -or $results.Count -eq 0) { if ($currentTries -lt $retryCount) { Write-LogFile -Message "[WARNING] The download encountered an issue and there might be incomplete data" -Color "Red" Write-LogFile -Message "[INFO] Sleeping 10 seconds before we try again" -Color "Red" Start-Sleep -Seconds 10 $currentTries = $currentTries + 1 continue } else{ Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")). Retry count reached. Moving forward!" break } } else { $currentTotal = $results[0].ResultCount $currentCount = $currentCount + $results.Count Write-LogFile -Message "[INFO] Found $currentTotal audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" if ($currentTotal -eq $results[$results.Count - 1].ResultIndex) { $message = "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range. Moving on!" if ($Output -eq "JSON") { $results = $results | ForEach-Object { $_.AuditData = $_.AuditData | ConvertFrom-Json $_ } $json = $results | ConvertTo-Json -Depth 100 $json | Out-File -Append "$OutputDir/UAL-$sessionID.json" -Encoding $Encoding Add-Content "$OutputDir/UAL-$sessionID.json" "`n" Write-LogFile -Message $message -Color "Green" } elseif ($Output -eq "CSV") { $results | export-CSV "$OutputDir/UAL-$sessionID.csv" -NoTypeInformation -Append -Encoding $Encoding Write-LogFile -Message $message -Color "Green" } break } } } $CurrentStart = $CurrentEnd } } if ($Output -eq "CSV" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "CSV" -MergedFileName "UAL-Combined.csv" } elseif ($Output -eq "JSON" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "JSON" -MergedFileName "UAL-Combined.json" } Write-LogFile -Message "[INFO] Acquisition complete, check the Output directory for your files.." -Color "Green" } function Get-UALGroup { <# .SYNOPSIS Gets the selected group of unified audit log entries. .DESCRIPTION Makes it possible to extract a group of specific unified audit data out of a Microsoft 365 environment. You can for example extract all Exchange or Azure logging in one go. The output will be written to: Output\UnifiedAuditLog\ .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .PARAMETER StartDate startDate is the parameter specifying the start date of the date range. Default: Today -90 days .PARAMETER EndDate endDate is the parameter specifying the end date of the date range. Default: Now .PARAMETER Interval Interval is the parameter specifying the interval in which the logs are being gathered. Default: 1440 minutes .PARAMETER Group Group is the group of logging needed to be extracted. Options are: Exchange, Azure, Sharepoint, Skype and Defender .PARAMETER Output Output is the parameter specifying the CSV or JSON output type. Default: CSV .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\UnifiedAuditLog .PARAMETER MergeOutput MergeOutput is the parameter specifying if you wish to merge CSV outputs to a single file. .PARAMETER Encoding Encoding is the parameter specifying the encoding of the CSV/JSON output file. Default: UTF8 .PARAMETER ObjecIDs The ObjectIds parameter filters the log entries by object ID. The object ID is the target object that was acted upon, and depends on the RecordType and Operations values of the event. You can enter multiple values separated by commas. .EXAMPLE Get-UALGroup -Group Azure Gets the Azure related unified audit log entries. .EXAMPLE Get-UALGroup -Group Exchange -UserIds Test@invictus-ir.com Gets the Exchange related unified audit log entries for the user Test@invictus-ir.com. .EXAMPLE Get-UALGroup -Group Exchange -UserIds "Test@invictus-ir.com,HR@invictus-ir.com" Gets all the unified audit log entries between 1/4/2023 and 5/4/2023 for the users Test@invictus-ir.com and HR@invictus-ir.com. .EXAMPLE Get-UALGroup -Group Azure -StartDate 1/4/2023 -EndDate 5/4/2023 Gets all the Azure related unified audit log entries between 1/4/2023 and 5/4/2023. .EXAMPLE Get-UALGroup -Group Defender -UserIds Test@invictus-ir.com -Interval 720 -Output JSON Gets all the Defender related unified audit log entries for the user Test@invictus-ir.com in JSON format with a time interval of 720. .EXAMPLE Get-UALGroup -Group Exchange -MergeOutput Gets the Azure related unified audit log entries and adds a combined output JSON file at the end of acquisition #> [CmdletBinding()] param( [string]$StartDate, [string]$EndDate, [string]$UserIds = "*", [string]$Interval = 1440, [string]$Group, [string]$Output = "CSV", [switch]$MergeOutput, [string]$OutputDir, [string]$Encoding = "UTF8", [string]$ObjectIds ) try { $areYouConnected = Get-AdminAuditLogConfig -ErrorAction stop } catch { write-logFile -Message "[INFO] Ensure you are connected to M365 by running the Connect-M365 command before executing this script" -Color "Yellow" Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" throw } if ($Group -eq "Exchange") { $recordTypes = "ExchangeAdmin","ExchangeAggregatedOperation","ExchangeItem","ExchangeItemGroup","ExchangeItemAggregated","ComplianceDLPExchange","ComplianceSupervisionExchange","MipAutoLabelExchangeItem" $recordFile = "Exchange" } elseif ($Group -eq "Azure") { $recordTypes = "AzureActiveDirectory","AzureActiveDirectoryAccountLogon","AzureActiveDirectoryStsLogon" $recordFile = "Azure" } elseif ($Group -eq "Sharepoint") { $recordTypes = "ComplianceDLPSharePoint","SharePoint","SharePointFileOperation","SharePointSharingOperation","SharepointListOperation", "ComplianceDLPSharePointClassification","SharePointCommentOperation", "SharePointListItemOperation", "SharePointContentTypeOperation", "SharePointFieldOperation","MipAutoLabelSharePointItem","MipAutoLabelSharePointPolicyLocation" $recordFile = "Sharepoint" } elseif ($Group -eq "Skype") { $recordTypes = "SkypeForBusinessCmdlets","SkypeForBusinessPSTNUsage","SkypeForBusinessUsersBlocked" $recordFile = "Skype" } elseif ($Group -eq "Defender") { $recordTypes = "ThreatIntelligence", "ThreatFinder","ThreatIntelligenceUrl","ThreatIntelligenceAtpContent","Campaign","AirInvestigation","WDATPAlerts","AirManualInvestigation","AirAdminActionInvestigation","MSTIC","MCASAlerts" $recordFile = "Defender" } else { Write-LogFile -Message "[WARNING] Invalid input. Select Exchange, Azure, Sharepoint, Defender or Skype" -Color red } write-logFile -Message "[INFO] Running Get-UALGroup" -Color "Green" StartDate EndDate if ($OutputDir -eq "" ){ $OutputDir = "Output\UnifiedAuditLog\$recordFile" if (!(test-path $OutputDir)) { write-logFile -Message "[INFO] Creating the following directory: $OutputDir" New-Item -ItemType Directory -Force -Name $OutputDir > $null } } else { if (Test-Path -Path $OutputDir) { write-LogFile -Message "[INFO] Custom directory set to: $OutputDir" } else { write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" } } write-logFile -Message "[INFO] Extracting all available audit logs between $($script:StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($script:EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" write-logFile -Message "[INFO] The following RecordType(s) are configured to be extracted:" $baseSearchQuery = @{ UserIds = $UserIds } if ($ObjectIds) { $baseSearchQuery.ObjectIds = $ObjectIds Write-LogFile -Message "[INFO] Filtering by ObjectIds: $ObjectIds" -Color "Green" } foreach ($record in $recordTypes) { write-logFile -Message "-$record" } foreach ($record in $recordTypes) { $resetInterval = $interval [DateTime]$currentStart = $script:StartDate [DateTime]$currentEnd = $script:EndDate $specificResult = Search-UnifiedAuditLog -StartDate $script:StartDate -EndDate $script:EndDate -RecordType $record @baseSearchQuery -ResultSize 1 | Format-List -Property ResultCount| out-string -Stream | select-string ResultCount if (($null -ne $specificResult) -and ($specificResult -ne 0)) { $number = $specificResult.tostring().split(":")[1] write-logFile -Message "[INFO]$($number) Records found for $record" -Color "Green" while ($currentStart -lt $script:EndDate) { $currentEnd = $currentStart.AddMinutes($Interval) $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record @baseSearchQuery -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($null -eq $amountResults) { Write-LogFile -Message "[INFO] No audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")). Moving on!" $CurrentStart = $CurrentEnd } elseif ($amountResults -gt 5000) { while ($amountResults -gt 5000) { $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record @baseSearchQuery -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($amountResults -lt 5000) { if ($Interval -eq 0) { Exit } } else { Write-LogFile -Message "[WARNING] $amountResults entries between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) exceeding the maximum of 5000 of entries" -Color "Red" $interval = [math]::Round(($Interval/(($amountResults/5000)*1.25)),2) $currentEnd = $currentStart.AddMinutes($Interval) Write-LogFile -Message "[INFO] Temporary lowering time interval to $Interval minutes" -Color "Yellow" } } } else { $Interval = $ResetInterval if ($currentEnd -gt $script:EndDate) { $currentEnd = $script:EndDate } $CurrentTries = 0 $SessionID = $currentStart.ToString("yyyyMMddHHmmss") while ($true) { [Array]$results = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record @baseSearchQuery -SessionCommand ReturnLargeSet -ResultSize $ResultSize $currentCount = 0 if ($null -eq $results -or $results.Count -eq 0) { if ($currentTries -lt $retryCount) { Write-LogFile -Message "[WARNING] The download encountered an issue and there might be incomplete data" -Color "Red" Write-LogFile -Message "[INFO] Sleeping 10 seconds before we try again" -Color "Red" Start-Sleep -Seconds 10 $currentTries = $currentTries + 1 continue } else{ Write-LogFile -Message "[WARNING] Empty data set returned between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")). Retry count reached. Moving forward!" break } } else { $currentTotal = $results[0].ResultCount $currentCount = $currentCount + $results.Count Write-LogFile -Message "[INFO] Found $currentTotal audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Green" if ($currentTotal -eq $results[$results.Count - 1].ResultIndex){ $message = "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range. Moving on!" if ($Output -eq "JSON") { $results = $results | ForEach-Object { $_.AuditData = $_.AuditData | ConvertFrom-Json $_ } $json = $results | ConvertTo-Json -Depth 100 $json | Out-File -Append "$OutputDir/UAL-$sessionID.json" -Encoding $Encoding Add-Content "$OutputDir/UAL-$sessionID.json" "`n" Write-LogFile -Message $message -Color "Green" } elseif ($Output -eq "CSV") { $results | export-CSV "$OutputDir/UAL-$sessionID.csv" -NoTypeInformation -Append -Encoding $Encoding Write-LogFile -Message $message -Color "Green" } break } } } } $currentStart = $currentEnd } } else { Write-LogFile -message "[INFO] No Records found for $Record" } } if ($Output -eq "CSV" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "CSV" -MergedFileName "UAL-Combined.csv" } elseif ($Output -eq "JSON" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "JSON" -MergedFileName "UAL-Combined.json" } Write-LogFile -Message "[INFO] Acquisition complete, check the Output directory for your files.." -Color "Green" } function Get-UALSpecific { <# .SYNOPSIS Gets specific record types of unified audit log. .DESCRIPTION Makes it possible to extract a group of specific unified audit data out of a Microsoft 365 environment. You can for example extract all Exchange or Azure logging in one go. The output will be written to: Output\UnifiedAuditLog\ .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .PARAMETER StartDate startDate is the parameter specifying the start date of the date range. Default: Today -90 days .PARAMETER EndDate endDate is the parameter specifying the end date of the date range. Default: Now .PARAMETER Interval Interval is the parameter specifying the interval in which the logs are being gathered. Default: 1440 minutes .PARAMETER RecordType The RecordType parameter filters the log entries by record type. Options are: ExchangeItem, ExchangeAdmin, etc. A total of 236 RecordTypes are supported. .PARAMETER Output Output is the parameter specifying the CSV or JSON output type. Default: CSV .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\UnifiedAuditLog .PARAMETER Encoding Encoding is the parameter specifying the encoding of the CSV output file. Default: UTF8 .PARAMETER MergeOutput MergeOutput is the parameter specifying if you wish to merge CSV/JSON outputs to a single file. .PARAMETER ObjecIDs The ObjectIds parameter filters the log entries by object ID. The object ID is the target object that was acted upon, and depends on the RecordType and Operations values of the event. You can enter multiple values separated by commas. .EXAMPLE Get-UALSpecific -RecordType ExchangeItem Gets the ExchangeItem logging from the unified audit log. .EXAMPLE Get-UALSpecific -RecordType MipAutoLabelExchangeItem -UserIds Test@invictus-ir.com Gets the MipAutoLabelExchangeItem logging from the unified audit log for the user Test@invictus-ir.com. .EXAMPLE Get-UALSpecific -RecordType PrivacyInsights -UserIds "Test@invictus-ir.com,HR@invictus-ir.com" Gets the PrivacyInsights logging from the unified audit log for the uses Test@invictus-ir.com and HR@invictus-ir.com. .EXAMPLE Get-UALSpecific -RecordType ExchangeAdmin -StartDate 1/4/2023 -EndDate 5/4/2023 Gets the ExchangeAdmin logging from the unified audit log entries between 1/4/2023 and 5/4/2023. .EXAMPLE Get-UALSpecific -RecordType MicrosoftFlow -UserIds Test@invictus-ir.com -StartDate 25/3/2023 -EndDate 5/4/2023 -Interval 720 -Output JSON Gets all the MicrosoftFlow logging from the unified audit log for the user Test@invictus-ir.com in JSON format with a time interval of 720. .EXAMPLE Get-UALSpecific -RecordType MipAutoLabelExchangeItem -MergeOutput Gets the ExchangeItem logging from the unified audit log and adds a combined output JSON file at the end of acquisition #> [CmdletBinding()] param( [string]$StartDate, [string]$EndDate, [string]$UserIds = "*", [string]$Interval = 1440, [Parameter(Mandatory=$true)]$RecordType, [string]$Output = "CSV", [switch]$MergeOutput, [string]$OutputDir, [string]$Encoding = "UTF8", [string]$ObjectIds ) try { $areYouConnected = Get-AdminAuditLogConfig -ErrorAction stop } catch { write-logFile -Message "[INFO] Ensure you are connected to M365 by running the Connect-M365 command before executing this script" -Color "Yellow" Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" throw } write-logFile -Message "[INFO] Running Get-UALSpecific" -Color "Green" StartDate EndDate if ($Output -eq "JSON") { $Output = "JSON" write-logFile -Message "[INFO] Output set to JSON" } else { $Output = "CSV" Write-LogFile -Message "[INFO] Output set to CSV" } write-logFile -Message "[INFO] Extracting all available audit logs between $($script:StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($script:EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" write-logFile -Message "[INFO] The following RecordType(s) are configured to be extracted:" $baseSearchQuery = @{ UserIds = $UserIds } if ($ObjectIds) { $baseSearchQuery.ObjectIds = $ObjectIds Write-LogFile -Message "[INFO] Filtering by ObjectIds: $ObjectIds" -Color "Green" } foreach ($record in $recordType) { write-logFile -Message "-$record" } foreach ($record in $recordType) { $resetInterval = $Interval [DateTime]$currentStart = $script:StartDate [DateTime]$currentEnd = $script:EndDate $specificResult = Search-UnifiedAuditLog -StartDate $script:StartDate -EndDate $script:EndDate -RecordType $record @baseSearchQuery -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if (($null -ne $specificResult) -and ($specificResult -ne 0)) { if ($OutputDir -eq "" ){ $OutputDir = "Output\UnifiedAuditLog\$record" if (!(test-path $OutputDir)) { write-logFile -Message "[INFO] Creating the following output directory: $OutputDir" New-Item -ItemType Directory -Force -Name $OutputDir > $null } } else { if (Test-Path -Path $OutputDir) { write-LogFile -Message "[INFO] Custom directory set to: $OutputDir" } else { write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" } } $number = $specificResult.tostring().split(":")[1] write-logFile -Message "[INFO]$($number) Records found for $record" -Color "Green" while ($currentStart -lt $script:EndDate) { $currentEnd = $currentStart.AddMinutes($Interval) $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record @baseSearchQuery -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($null -eq $amountResults) { Write-LogFile -Message "[INFO] No audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")). Moving on!" $CurrentStart = $CurrentEnd } elseif ($amountResults -gt 5000) { while ($amountResults -gt 5000) { $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record @baseSearchQuery -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($amountResults -lt 5000) { if ($Interval -eq 0) { Exit } } else { Write-LogFile -Message "[WARNING] $amountResults entries between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) exceeding the maximum of 5000 of entries" -Color "Red" $interval = [math]::Round(($Interval/(($amountResults/5000)*1.25)),2) $currentEnd = $currentStart.AddMinutes($Interval) Write-LogFile -Message "[INFO] Temporary lowering time interval to $Interval minutes" -Color "Yellow" } } } else { $Interval = $ResetInterval if ($currentEnd -gt $script:endDate) { $currentEnd = $script:endDate } $currentTries = 0 $sessionID = $currentStart.ToString("yyyyMMddHHmmss") while ($true) { [Array]$results = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -RecordType $record @baseSearchQuery -SessionCommand ReturnLargeSet -ResultSize $ResultSize $CurrentCount = 0 if ($null -eq $results -or $results.Count -eq 0) { if ($currentTries -lt $retryCount) { $currentTries = $currentTries + 1 continue } else { Write-LogFile -Message "[INFO] No audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" break } } $currentTotal = $results[0].ResultCount $currentCount = $currentCount + $results.Count Write-LogFile -Message "[INFO] Found $currentTotal audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" -Color "Green" if ($currentTotal -eq $results[$results.Count - 1].ResultIndex) { $message = "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range. Moving on!" if ($Output -eq "JSON") { $results = $results | ForEach-Object { $_.AuditData = $_.AuditData | ConvertFrom-Json $_ } $json = $results | ConvertTo-Json -Depth 100 $json | Out-File -Append "$OutputDir/UAL-$sessionID.json" -Encoding $Encoding Add-Content "$OutputDir/UAL-$sessionID.json" "`n" Write-LogFile -Message $message -Color "Green" } elseif ($Output -eq "CSV") { $results | export-CSV "$OutputDir/UAL-$sessionID.csv" -NoTypeInformation -Append -Encoding $Encoding Write-LogFile -Message $message -Color "Green" } break } } } $currentStart = $currentEnd } } else { Write-LogFile -Message "[INFO] No Records found for $record" } } if ($Output -eq "CSV" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "CSV" -MergedFileName "UAL-Combined.csv" } elseif ($Output -eq "JSON" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "JSON" -MergedFileName "UAL-Combined.json" } Write-LogFile -Message "[INFO] Acquisition complete, check the Output directory for your files.." -Color "Green" } function Get-UALSpecificActivity { <# .SYNOPSIS Gets specific activities from the unified audit log. .DESCRIPTION Makes it possible to extract a group of specific unified audit activities out of a Microsoft 365 environment. You can for example extract all Inbox Rules or Azure Changes in one go. The output will be written to: Output\UnifiedAuditLog\ .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .PARAMETER StartDate startDate is the parameter specifying the start date of the date range. Default: Today -90 days .PARAMETER EndDate endDate is the parameter specifying the end date of the date range. Default: Now .PARAMETER Interval Interval is the parameter specifying the interval in which the logs are being gathered. Default: 1440 minutes .PARAMETER ActivityType The ActivityType parameter filters the log entries by operation or activity type. Options are: New-MailboxRule, MailItemsAccessed, etc. A total of 108 common ActivityTypes are supported. .PARAMETER Output Output is the parameter specifying the CSV or JSON output type. Default: CSV .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\UnifiedAuditLog .PARAMETER MergeOutput MergeOutput is the parameter specifying if you wish to merge CSV/JSON outputs into a single file at the end of the acquisition. .PARAMETER Encoding Encoding is the parameter specifying the encoding of the CSV/JSON output file. Default: UTF8 .EXAMPLE Get-UALSpecificActivity -ActivityType New-InboxRule Gets the New-InboxRule logging from the unified audit log. .EXAMPLE Get-UALSpecificActivity -ActivityType FileDownloaded -UserIds Test@invictus-ir.com Gets the Sharepoint FileDownload logging from the unified audit log for the user Test@invictus-ir.com. .EXAMPLE Get-UALSpecificActivity -ActivityType Add service principal. -UserIds "Test@invictus-ir.com,HR@invictus-ir.com" Gets the Add Service Principal. logging from the unified audit log for the uses Test@invictus-ir.com and HR@invictus-ir.com. .EXAMPLE Get-UALSpecificActivity -ActivityType MailItemsAccessed -StartDate 1/4/2023 -EndDate 5/4/2023 Gets the MailItemsAccessed logging from the unified audit log entries between 1/4/2023 and 5/4/2023. .EXAMPLE Get-UALSpecificActivity -ActivityType MailItemsAccessed -UserIds Test@invictus-ir.com -StartDate 25/3/2023 -EndDate 5/4/2023 -Interval 720 -Output JSON Gets all the MailItemsAccessed logging from the unified audit log for the user Test@invictus-ir.com in JSON format with a time interval of 720. #> [CmdletBinding()] param( [string]$StartDate, [string]$EndDate, [string]$UserIds = "*", [string]$Interval = 1440, [Parameter(Mandatory=$true)]$ActivityType, [string]$Output = "CSV", [string]$OutputDir, [string]$Encoding = "UTF8", [switch]$MergeOutput ) try { $areYouConnected = Get-AdminAuditLogConfig -ErrorAction stop } catch { write-logFile -Message "[INFO] Ensure you are connected to M365 by running the Connect-M365 command before executing this script" -Color "Yellow" throw } write-logFile -Message "[INFO] Running Get-UALSpecificActivity" -Color "Green" StartDate EndDate if ($Output -eq "JSON") { $Output = "JSON" write-logFile -Message "[INFO] Output set to JSON" } else { $Output = "CSV" write-logFile -Message "[INFO] Output set to CSV" } write-logFile -Message "[INFO] Extracting all available audit logs between $($script:StartDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($script:EndDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" write-logFile -Message "[INFO] The following ActivityType(s) are configured to be extracted:" foreach ($record in $ActivityType) { write-logFile -Message "-$record" } foreach ($record in $ActivityType) { $resetInterval = $Interval [DateTime]$currentStart = $script:StartDate [DateTime]$currentEnd = $script:EndDate $specificResult = Search-UnifiedAuditLog -StartDate $script:StartDate -EndDate $script:EndDate -Operations $record -UserIds $UserIds -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if (($null -ne $specificResult) -and ($specificResult -ne 0)) { if ($OutputDir -eq "" ){ $OutputDir = "Output\UnifiedAuditLog\$record\" if (!(test-path $OutputDir)) { write-logFile -Message "[INFO] Creating the following output directory: $OutputDir" New-Item -ItemType Directory -Force -Name $OutputDir > $null } } else { if (Test-Path -Path $OutputDir) { write-LogFile -Message "[INFO] Custom directory set to: $OutputDir" } else { write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop write-LogFile -Message "[Error] Custom directory invalid: $OutputDir exiting script" } } $number = $specificResult.tostring().split(":")[1] write-logFile -Message "[INFO]$($number) Records found for $record" -Color "Green" while ($currentStart -lt $script:EndDate) { $currentEnd = $currentStart.AddMinutes($Interval) $amountResults = Search-UnifiedAuditLog -UserIds $UserIds -StartDate $currentStart -EndDate $currentEnd -Operations $record -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($null -eq $amountResults) { Write-LogFile -Message "[INFO] No audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")). Moving on!" $CurrentStart = $CurrentEnd } elseif ($amountResults -gt 5000) { while ($amountResults -gt 5000) { $amountResults = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -UserIds $UserIds -Operations $record -ResultSize 1 | Select-Object -First 1 -ExpandProperty ResultCount if ($amountResults -lt 5000) { if ($Interval -eq 0) { Exit } } else { Write-LogFile -Message "[WARNING] $amountResults entries between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) exceeding the maximum of 5000 of entries" -Color "Red" $interval = [math]::Round(($Interval/(($amountResults/5000)*1.25)),2) $currentEnd = $currentStart.AddMinutes($Interval) Write-LogFile -Message "[INFO] Temporary lowering time interval to $Interval minutes" -Color "Yellow" } } } else { $Interval = $ResetInterval if ($currentEnd -gt $script:endDate) { $currentEnd = $script:endDate } $currentTries = 0 $sessionID = $currentStart.ToString("yyyyMMddHHmmss") while ($true) { [Array]$results = Search-UnifiedAuditLog -StartDate $currentStart -EndDate $currentEnd -UserIds $UserIds -Operations $record -SessionCommand ReturnLargeSet -ResultSize $ResultSize $CurrentCount = 0 if ($null -eq $results -or $results.Count -eq 0) { if ($currentTries -lt $retryCount) { $currentTries = $currentTries + 1 continue } else { Write-LogFile -Message "[INFO] No audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" break } } $currentTotal = $results[0].ResultCount $currentCount = $currentCount + $results.Count Write-LogFile -Message "[INFO] Found $currentTotal audit logs between $($currentStart.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK")) and $($currentEnd.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssK"))" if ($currentTotal -eq $results[$results.Count - 1].ResultIndex) { $message = "[INFO] Successfully retrieved $($currentCount) records out of total $($currentTotal) for the current time range. Moving on!" if ($Output -eq "JSON") { $results = $results | ForEach-Object { $_.AuditData = $_.AuditData | ConvertFrom-Json $_ } $json = $results | ConvertTo-Json -Depth 100 $json | Out-File -Append "$OutputDir/UAL-$sessionID.json" -Encoding $Encoding Add-Content "$OutputDir/UAL-$sessionID.json" "`n" Write-LogFile -Message $message -Color "Green" } elseif ($Output -eq "CSV") { $results | export-CSV "$OutputDir/UAL-$sessionID.csv" -NoTypeInformation -Append -Encoding $Encoding Write-LogFile -Message $message -Color "Green" } break } } } $currentStart = $currentEnd } } else { Write-LogFile -Message "[INFO] No Records found for $record" } } if ($Output -eq "CSV" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "CSV" -MergedFileName "UAL-Combined.csv" } elseif ($Output -eq "JSON" -and ($MergeOutput.IsPresent)) { Write-LogFile -Message "[INFO] Merging output files into one file" Merge-OutputFiles -OutputDir $OutputDir -OutputType "JSON" -MergedFileName "UAL-Combined.json" } Write-LogFile -Message "[INFO] Acquisition complete, check the Output directory for your files.." -Color "Green" } |