Scripts/Get-AzureEntraGraphLogs.ps1
function Get-GraphEntraSignInLogs { <# .SYNOPSIS Gets of sign-ins logs. .DESCRIPTION The Get-GraphEntraSignInLogs GraphAPI cmdlet collects the contents of the Azure Entra ID sign-in logs. .PARAMETER startDate The startDate parameter specifies the date from which all logs need to be collected. .PARAMETER endDate The Before parameter specifies the date endDate which all logs need to be collected. .PARAMETER MergeOutput MergeOutput is the parameter specifying if you wish to merge outputs to a single file Default: No .PARAMETER Output Output is the parameter specifying the JSON or SOF-ELK output type. The SOF-ELK output can be imported into the platform of the same name. Default: JSON .PARAMETER LogLevel Specifies the level of logging: None: No logging Minimal: Critical errors only Standard: Normal operational logging Debug: Verbose logging for debugging purposes Default: Standard .PARAMETER OutputDir outputDir is the parameter specifying the output directory. Default: The output will be written to: Output\EntraID\{date_SignInLogs}\{timestamp}-{eventType}-SignInLogs.json .PARAMETER Encoding Encoding is the parameter specifying the encoding of the JSON output file. Default: UTF8 .PARAMETER EventTypes Specifies which types of sign-in events to collect. Can be one or more of: - All: Collects all event types (default) - interactiveUser: User sign-ins requiring user interaction - nonInteractiveUser: Automated user sign-ins - servicePrincipal: Application sign-ins - managedIdentity: Azure managed identity sign-ins Default: 'All' .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .EXAMPLE Get-GraphEntraSignInLogs Get all audit logs of sign-ins. .EXAMPLE Get-GraphEntraSignInLogs -Application Get all audit logs of sign-ins via application authentication. .EXAMPLE Get-GraphEntraSignInLogs -endDate 2025-04-12 Get audit logs before 2025-04-12. .EXAMPLE Get-GraphEntraSignInLogs -startDate 2025-04-12 Get audit logs after 2025-04-12. EXAMPLE Get-GraphEntraSignInLogs -EventTypes interactiveUser Get only interactive user sign-in logs. .EXAMPLE Get-GraphEntraSignInLogs -EventTypes interactiveUser,servicePrincipal Get both interactive user and service principal sign-in logs. .EXAMPLE Get-GraphEntraSignInLogs -Output SOF-ELK -MergeOutput Get the Entra ID SignIn Log in a format compatible with the SOF-ELK platform and merge all data into a single file. #> [CmdletBinding()] param( [string]$startDate, [string]$endDate, [ValidateSet("JSON", "SOF-ELK")] [string]$Output = "JSON", [string]$OutputDir, [string[]]$UserIds, [switch]$MergeOutput, [string]$Encoding = "UTF8", [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard', [Parameter()] [ValidateSet('All', 'interactiveUser', 'nonInteractiveUser', 'servicePrincipal', 'managedIdentity')] [string[]]$EventTypes = @('All') ) Set-LogLevel -Level ([LogLevel]::$LogLevel) $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug $summary = @{ TotalRecords = 0 StartTime = Get-Date ProcessingTime = $null TotalFiles = 0 } if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug Write-LogFile -Message "[DEBUG] StartDate: $startDate" -Level Debug Write-LogFile -Message "[DEBUG] EndDate: $endDate" -Level Debug Write-LogFile -Message "[DEBUG] Output: $Output" -Level Debug Write-LogFile -Message "[DEBUG] OutputDir: $OutputDir" -Level Debug Write-LogFile -Message "[DEBUG] UserIds: $UserIds" -Level Debug Write-LogFile -Message "[DEBUG] MergeOutput: $($MergeOutput.IsPresent)" -Level Debug Write-LogFile -Message "[DEBUG] Encoding: $Encoding" -Level Debug Write-LogFile -Message "[DEBUG] LogLevel: $LogLevel" -Level Debug Write-LogFile -Message "[DEBUG] EventTypes: $($EventTypes -join ', ')" -Level Debug $graphModule = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue if ($graphModule) { Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug foreach ($module in $graphModule) { Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug } } else { Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug } } Write-LogFile -Message "=== Starting Sign-in Log Collection ===" -Color "Cyan" -Level Standard $requiredScopes = @("AuditLog.Read.All", "Directory.Read.All") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug if ($graphAuth.MissingScopes.Count -gt 0) { Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug } else { Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug } } $date = [datetime]::Now.ToString('yyyyMMdd') if ($OutputDir -eq "" ){ $OutputDir = "Output\EntraID\$($date)-SignInLogs" if (!(test-path $OutputDir)) { New-Item -ItemType Directory -Force -path $OutputDir > $null } } else { if (!(Test-Path -Path $OutputDir)) { Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir" -Level Minimal -Color "Red" return } } StartDateAz -Quiet EndDate -Quiet $StartDate = $script:StartDate.ToString('yyyy-MM-ddTHH:mm:ssZ') $EndDate = $script:EndDate.ToString('yyyy-MM-ddTHH:mm:ssZ') Write-LogFile -Message "Start Date: $StartDate" -Level Standard Write-LogFile -Message "End Date: $EndDate" -Level Standard Write-LogFile -Message "Output Format: $Output" -Level Standard Write-LogFile -Message "Output Directory: $OutputDir" -Level Standard if ($UserIds) { Write-LogFile -Message "Filtering for User: $UserIds" -Level Standard } Write-LogFile -Message "----------------------------------------`n" -Level Standard $eventTypeMapping = @{ 'interactiveUser' = @{ displayName = 'interactiveUser' filename = 'interactiveUser' filterQuery = "(signInEventTypes/any(t: t eq 'interactiveUser'))" } 'nonInteractiveUser' = @{ displayName = 'nonInteractiveUser' filename = 'nonInteractiveUser' filterQuery = "(signInEventTypes/any(t: t eq 'nonInteractiveUser'))" } 'combinedUser' = @{ displayName = 'interactiveUser & nonInteractiveUser' filename = 'interactiveUser-nonInteractiveUser' filterQuery = "(signInEventTypes/any(t: t eq 'interactiveUser' or t eq 'nonInteractiveUser'))" } 'servicePrincipal' = @{ displayName = 'servicePrincipal' filename = 'servicePrincipal' filterQuery = "(signInEventTypes/any(t: t eq 'servicePrincipal'))" } 'managedIdentity' = @{ displayName = 'managedIdentity' filename = 'managedIdentity' filterQuery = "(signInEventTypes/any(t: t eq 'managedIdentity'))" } } $eventTypesToProcess = @() if ($EventTypes -contains 'All') { if ($UserIds -and $UserIds.Count -gt 0) { $eventTypesToProcess = @('combinedUser') Write-LogFile -Message "[INFO] Filtering by users - skipping servicePrincipal and managedIdentity (will be empty)" -Level Standard -Color "Yellow" } else { $eventTypesToProcess = @('combinedUser', 'servicePrincipal', 'managedIdentity') } } elseif ($EventTypes -contains 'interactiveUser' -and $EventTypes -contains 'nonInteractiveUser') { $remainingTypes = $EventTypes | Where-Object { $_ -ne 'interactiveUser' -and $_ -ne 'nonInteractiveUser' } $eventTypesToProcess = @('combinedUser') + $remainingTypes } else { $eventTypesToProcess = $EventTypes } foreach ($eventType in $eventTypesToProcess) { $currentEventType = $eventTypeMapping[$eventType] Write-LogFile -Message "[INFO] Acquiring the $($currentEventType.displayName) sign-in logs" -Level Standard -Color "Cyan" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Event type configuration:" -Level Debug Write-LogFile -Message "[DEBUG] Display name: $($currentEventType.displayName)" -Level Debug Write-LogFile -Message "[DEBUG] Filename pattern: $($currentEventType.filename)" -Level Debug Write-LogFile -Message "[DEBUG] Filter query: $($currentEventType.filterQuery)" -Level Debug } $eventTypeDir = Join-Path -Path $OutputDir -ChildPath $currentEventType.displayName if (!(Test-Path $eventTypeDir)) { New-Item -ItemType Directory -Force -Path $eventTypeDir > $null } $filterQuery = "createdDateTime ge $StartDate and createdDateTime le $EndDate" if ($UserIds -and $UserIds.Count -gt 0) { $userFilters = $UserIds | ForEach-Object { "startsWith(userPrincipalName, '$_')" } $filterQuery += " and (" + ($userFilters -join " or ") + ")" } $filterQuery += " and $($currentEventType.filterQuery)" $encodedFilterQuery = [System.Web.HttpUtility]::UrlEncode($filterQuery) $apiUrl = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=$encodedFilterQuery" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] API configuration:" -Level Debug Write-LogFile -Message "[DEBUG] Base URL: https://graph.microsoft.com/beta/auditLogs/signIns" -Level Debug Write-LogFile -Message "[DEBUG] Filter query (decoded): $filterQuery" -Level Debug Write-LogFile -Message "[DEBUG] Full API URL: $apiUrl" -Level Debug } $eventTypeSummary = @{ EventType = $currentEventType.displayName RecordCount = 0 Files = 0 } try { Do { $retryCount = 0 $maxRetries = 3 $success = $false $tokenRetryCount = 0 $maxTokenRetries = 5 while (-not $success -and $retryCount -lt $maxRetries) { try { $response = Invoke-MgGraphRequest -Uri $apiUrl -Method Get -ContentType "application/json; odata.metadata=minimal; odata.streaming=true;" -OutputType Json $responseJson = $response | ConvertFrom-Json $success = $true } catch { if (($_.Exception.Message -like "*Skip token is null*" -or $_.Exception.Message -like "*token*expired*" -or $_.Exception.Message -like "*Bad Request*") -and $tokenRetryCount -lt $maxTokenRetries) { $tokenRetryCount++ Write-LogFile -Message "[WARNING] Token expired or invalid. Reconnecting and retrying... Attempt $tokenRetryCount of $maxTokenRetries" -Level Standard -Color "Yellow" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Token error details:" -Level Debug Write-LogFile -Message "[DEBUG] Error message: $($_.Exception.Message)" -Level Debug Write-LogFile -Message "[DEBUG] Token retry count: $tokenRetryCount" -Level Debug } # Re-authenticate to refresh the token $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes -Force Start-Sleep -Seconds 20 continue } $retryCount++ if ($retryCount -lt $maxRetries) { Write-LogFile -Message "[WARNING] Failed to acquire logs. Retrying... Attempt $retryCount of $maxRetries" -Level Standard -Color "Yellow" Start-Sleep -Seconds 15 if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] API call error details:" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug } } else { Write-LogFile -Message "[ERROR] Failed to acquire logs after $maxRetries attempts. Error: $($_.Exception.Message)" -Level Minimal -Color "Red" throw } } } if ($responseJson.value) { $date = [datetime]::Now.ToString('yyyyMMddHHmmss') $filePath = Join-Path -Path $eventTypeDir -ChildPath "$($date)-$($currentEventType.filename)-SignInLogs.json" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing response data:" -Level Debug Write-LogFile -Message "[DEBUG] Records in batch: $($responseJson.value.Count)" -Level Debug Write-LogFile -Message "[DEBUG] Output file: $filePath" -Level Debug } if ($Output -eq "JSON" ) { $responseJson.value | ConvertTo-Json -Depth 100 | Out-File -FilePath $filePath -Append -Encoding $Encoding } elseif ($Output -eq "SOF-ELK"){ # UTF8 is fixed, as it is required by SOF-ELK foreach ($item in $responseJson.value) { $item | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $filePath -Append -Encoding UTF8 } } $currentBatchCount = ($responseJson.value | Measure-Object).Count $summary.TotalRecords += $currentBatchCount $summary.TotalFiles++ $eventTypeSummary.RecordCount += $currentBatchCount $eventTypeSummary.Files++ $dates = $responseJson.value | ForEach-Object { [DateTime]::Parse($_.CreatedDateTime, [System.Globalization.CultureInfo]::InvariantCulture) } | Sort-Object $from = $dates | Select-Object -First 1 $to = ($dates | Select-Object -Last 1) Write-LogFile -Message "[INFO] Retrieved $currentBatchCount records between $from and $to" -Level Standard -Color "Green" } $apiUrl = $responseJson.'@odata.nextLink' } While ($apiUrl) if ($MergeOutput.IsPresent) { Write-LogFile -Message "[INFO] Merging output files for $eventType" -Level Standard if ($Output -eq "JSON") { Merge-OutputFiles -OutputDir $eventTypeDir -OutputType "JSON" -MergedFileName "SignInLogs-$($currentEventType.filename)-Combined.json" } elseif ($Output -eq "SOF-ELK") { Merge-OutputFiles -OutputDir $eventTypeDir -OutputType "SOF-ELK" -MergedFileName "SignInLogs-$eventType-Combined.json" } } Write-LogFile -Message "`nSummary for $($currentEventType.displayName):" -Color "Cyan" -Level Standard Write-LogFile -Message " Records: $($eventTypeSummary.RecordCount)" -Level Standard Write-LogFile -Message " Files: $($eventTypeSummary.Files)`n" -Level Standard } catch { Write-LogFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Level Minimal -Color "Red" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Event type processing error:" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug } throw } } $summary.ProcessingTime = (Get-Date) - $summary.StartTime Write-LogFile -Message "`nOverall Collection Summary:" -Color "Cyan" -Level Standard Write-LogFile -Message " Total Records: $($summary.TotalRecords)" -Level Standard Write-LogFile -Message " Files Created: $($summary.TotalFiles)" -Level Standard Write-LogFile -Message " Output Directory: $OutputDir" -Level Standard Write-LogFile -Message " Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Level Standard -Color "Green" } function Get-GraphEntraAuditLogs { <# .SYNOPSIS Get directory audit logs. .DESCRIPTION The Get-GraphEntraAuditLogs GraphAPI cmdlet to collect the contents of the Entra ID Audit logs. .PARAMETER startDate The startDate parameter specifies the date from which all logs need to be collected. .PARAMETER endDate The Before parameter specifies the date endDate which all logs need to be collected. .PARAMETER OutputDir outputDir is the parameter specifying the output directory. Default: The output will be written to: "Output\EntraID\{date_AuditLogs}\Auditlogs.json .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .PARAMETER All When specified along with UserIds, this parameter filters the results to include events where the provided UserIds match any user principal name found in either the userPrincipalNames or targetResources fields. .PARAMETER Encoding Encoding is the parameter specifying the encoding of the JSON output file. Default: UTF8 .PARAMETER MergeOutput MergeOutput is the parameter specifying if you wish to merge outputs to a single file Default: No .PARAMETER Output Output is the parameter specifying the JSON or SOF-ELK output type. The SOF-ELK output can be imported into the platform of the same name. Default: JSON .EXAMPLE Get-GraphEntraAuditLogs Get directory audit logs. .EXAMPLE Get-GraphEntraAuditLogs -Application Get directory audit logs via application authentication. .PARAMETER LogLevel Specifies the level of logging: None: No logging Minimal: Critical errors only Standard: Normal operational logging Default: Standard .EXAMPLE Get-GraphEntraAuditLogs -UserIds 'user@example.com' -All Get sign-in logs for 'user@example.com', including both userPrincipalName and targetResources in the filter. .EXAMPLE Get-GraphEntraAuditLogs -Before 2025-04-12 Get directory audit logs before 2025-04-12. .EXAMPLE Get-GraphEntraAuditLogs -After 2025-04-12 Get directory audit logs after 2025-04-12. #> [CmdletBinding()] param( [string]$startDate, [string]$endDate, [string]$OutputDir, [ValidateSet("JSON", "SOF-ELK")] [string]$Output = "JSON", [string]$Encoding = "UTF8", [switch]$MergeOutput, [string[]]$UserIds, [switch]$All, [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard' ) Set-LogLevel -Level ([LogLevel]::$LogLevel) $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug $summary = @{ TotalRecords = 0 StartTime = Get-Date ProcessingTime = $null TotalFiles = 0 } if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug Write-LogFile -Message "[DEBUG] StartDate: $startDate" -Level Debug Write-LogFile -Message "[DEBUG] EndDate: $endDate" -Level Debug Write-LogFile -Message "[DEBUG] Output: $Output" -Level Debug Write-LogFile -Message "[DEBUG] OutputDir: $OutputDir" -Level Debug Write-LogFile -Message "[DEBUG] UserIds: $UserIds" -Level Debug Write-LogFile -Message "[DEBUG] All: $($All.IsPresent)" -Level Debug Write-LogFile -Message "[DEBUG] MergeOutput: $($MergeOutput.IsPresent)" -Level Debug Write-LogFile -Message "[DEBUG] Encoding: $Encoding" -Level Debug Write-LogFile -Message "[DEBUG] LogLevel: $LogLevel" -Level Debug $graphModule = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue if ($graphModule) { Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug foreach ($module in $graphModule) { Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug } } else { Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug } } Write-LogFile -Message "=== Starting Audit Log Collection ===" -Color "Cyan" -Level Standard $requiredScopes = @("AuditLog.Read.All", "Directory.Read.All") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug if ($graphAuth.MissingScopes.Count -gt 0) { Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug } else { Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug } } $date = [datetime]::Now.ToString('yyyyMMdd') if ($OutputDir -eq "" ){ $OutputDir = "Output\EntraID\$($date)-Auditlogs" if (!(test-path $OutputDir)) { New-Item -ItemType Directory -Force -Path $OutputDir > $null write-logFile -Message "[INFO] Creating the following directory: $OutputDir" } } else { if (!(Test-Path -Path $OutputDir)) { Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir" -Level Minimal -Color "Red" return } } StartDateAz -Quiet EndDate -Quiet $StartDate = $script:StartDate.ToString('yyyy-MM-ddTHH:mm:ssZ') $EndDate = $script:EndDate.ToString('yyyy-MM-ddTHH:mm:ssZ') Write-LogFile -Message "Start Date: $StartDate" -Level Standard Write-LogFile -Message "End Date: $EndDate" -Level Standard Write-LogFile -Message "Output Format: $Output" -Level Standard Write-LogFile -Message "Output Directory: $OutputDir" -Level Standard if ($UserIds) { Write-LogFile -Message "Filtering for User: $UserIds" -Level Standard } Write-LogFile -Message "----------------------------------------`n" -Level Standard $filterQuery = "activityDateTime ge $StartDate and activityDateTime le $EndDate" if ($UserIds -and $UserIds.Count -gt 0) { $userFilters = $UserIds | ForEach-Object { "startsWith(initiatedBy/user/userPrincipalName, '$_')" } $filterQuery += " and (" + ($userFilters -join " or ") + ")" if ($All.IsPresent) { $targetFilters = $UserIds | ForEach-Object { "targetResources/any(tr: tr/userPrincipalName eq '$_')" } $filterQuery = "($filterQuery) or (" + ($targetFilters -join " or ") + ")" } } else { if ($All.IsPresent) { Write-LogFile -Message "[WARNING] '-All' switch has no effect without specifying UserIds" } } $encodedFilterQuery = [System.Web.HttpUtility]::UrlEncode($filterQuery) $apiUrl = "https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?`$filter=$encodedFilterQuery" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] API configuration:" -Level Debug Write-LogFile -Message "[DEBUG] Base URL: https://graph.microsoft.com/v1.0/auditLogs/directoryAudits" -Level Debug Write-LogFile -Message "[DEBUG] Filter query (decoded): $filterQuery" -Level Debug Write-LogFile -Message "[DEBUG] Full API URL: $apiUrl" -Level Debug } try { Do { $retryCount = 0 $maxRetries = 3 $success = $false while (-not $success -and $retryCount -lt $maxRetries) { try { $response = Invoke-MgGraphRequest -Uri $apiUrl -Method Get -ContentType "application/json; odata.metadata=minimal; odata.streaming=true;" -OutputType Json $responseJson = $response | ConvertFrom-Json $success = $true } catch { $retryCount++ if ($retryCount -lt $maxRetries) { Write-LogFile -Message "[WARNING] Failed to acquire logs. Retrying... Attempt $retryCount of $maxRetries" -Level Standard -Color "Yellow" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] API call error details:" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug } Start-Sleep -Seconds 15 } else { Write-LogFile -Message "[ERROR] Failed to acquire logs after $maxRetries attempts. Error: $($_.Exception.Message)" -Level Minimal -Color "Red" throw } } } if ($responseJson.value) { $date = [datetime]::Now.ToString('yyyyMMddHHmmss') $filePath = Join-Path -Path $OutputDir -ChildPath "$($date)-AuditLogs.json" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing response data:" -Level Debug Write-LogFile -Message "[DEBUG] Records in batch: $($responseJson.value.Count)" -Level Debug Write-LogFile -Message "[DEBUG] Output file: $filePath" -Level Debug } if ($Output -eq "JSON") { $responseJson.value | ConvertTo-Json -Depth 100 | Out-File -FilePath $filePath -Append -Encoding $Encoding } elseif ($Output -eq "SOF-ELK") { # UTF8 is fixed, as it is required by SOF-ELK foreach ($item in $responseJson.value) { $item | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $filePath -Append -Encoding UTF8 } } $currentBatchCount = ($responseJson.value | Measure-Object).Count $summary.TotalRecords += $currentBatchCount $summary.TotalFiles++ $dates = $responseJson.value | ForEach-Object { [DateTime]::Parse($_.activityDateTime, [System.Globalization.CultureInfo]::InvariantCulture) } | Sort-Object $from = $dates | Select-Object -First 1 $fromstr = $from.ToString('yyyy-MM-ddTHH:mmZ') $to = ($dates | Select-Object -Last 1).ToString('yyyy-MM-ddTHH:mmZ') Write-LogFile -Message "[INFO] Retrieved $currentBatchCount records between $fromstr and $to" -Level Standard -Color "Green" } $apiUrl = $responseJson.'@odata.nextLink' } While ($apiUrl) if ($Output -eq "JSON" -and ($MergeOutput.IsPresent)) { Merge-OutputFiles -OutputDir $OutputDir -OutputType "JSON" -MergedFileName "AuditLogs-Combined.json" } elseif ($Output -eq "SOF-ELK" -and ($MergeOutput.IsPresent)) { Merge-OutputFiles -OutputDir $OutputDir -OutputType "SOF-ELK" -MergedFileName "AuditLogs-Combined.json" } $summary.ProcessingTime = (Get-Date) - $summary.StartTime Write-LogFile -Message "`nCollection Summary:" -Color "Cyan" -Level Standard Write-LogFile -Message " Total Records: $($summary.TotalRecords)" -Level Standard Write-LogFile -Message " Files Created: $($summary.TotalFiles)" -Level Standard Write-LogFile -Message " Output Directory: $OutputDir" -Level Standard Write-LogFile -Message " Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Level Standard -Color "Green" } catch { Write-logFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Fatal error details:" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Full error: $($_.Exception.ToString())" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug Write-LogFile -Message "[DEBUG] Records collected before error: $($summary.TotalRecords)" -Level Debug } throw } } |