From e96ee88e8d41ec011afacaed00f6378d613a13d0 Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Sun, 8 Dec 2024 13:39:07 -0500 Subject: [PATCH] Adds abilityt to parse and write simple_user_changes and user_changes. User changes still has raw auditlog blob in it. --- .../functions/User/Get-HawkUserAdminAudit.ps1 | 95 ++++++++++--------- .../functions/Get-SimpleUnifiedAuditLog.ps1 | 85 ++++++----------- 2 files changed, 80 insertions(+), 100 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserAdminAudit.ps1 b/Hawk/functions/User/Get-HawkUserAdminAudit.ps1 index f76e259..9bd9de3 100644 --- a/Hawk/functions/User/Get-HawkUserAdminAudit.ps1 +++ b/Hawk/functions/User/Get-HawkUserAdminAudit.ps1 @@ -8,69 +8,78 @@ .PARAMETER UserPrincipalName UserPrincipalName of the user you're investigating .OUTPUTS - File: Simple_User_Changes.csv - Path: + Path: \ Description: All cmdlets that were run against the user in a simple format. + + File: User_Changes.csv + Path: \ + Description: Raw data of all changes made to the user. + + File: User_Changes_Raw.json + Path: \ + Description: Raw JSON data from audit logs. + + File: User_Changes_Raw.txt + Path: \ + Description: Human readable format of raw audit data. .EXAMPLE Get-HawkUserAdminAudit -UserPrincipalName user@company.com Gets all changes made to user@company.com and outputs them to the csv and json files. #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [array]$UserPrincipalName + ) - param - ( - [Parameter(Mandatory = $true)] - [array]$UserPrincipalName - ) + Test-EXOConnection + Send-AIEvent -Event "CmdRun" - Test-EXOConnection - Send-AIEvent -Event "CmdRun" + # Verify our UPN input + [array]$UserArray = Test-UserObject -ToTest $UserPrincipalName - # Verify our UPN input - [array]$UserArray = Test-UserObject -ToTest $UserPrincipalName + foreach ($Object in $UserArray) { + [string]$User = $Object.UserPrincipalName - foreach ($Object in $UserArray) { - [string]$User = $Object.UserPrincipalName + # Get the mailbox name since that is what we store in the admin audit log + $MailboxName = (Get-Mailbox -Identity $User).Name - # Get the mailbox name (used previously) - $MailboxName = (Get-Mailbox -Identity $User).Name + Out-LogFile ("Searching for changes made to: " + $MailboxName) -action - Out-LogFile ("Searching for changes made to: " + $MailboxName) -action + # Get all changes for this user + [array]$UserChanges = Search-UnifiedAuditLog -UserIds $User -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate -RecordType ExchangeAdmin -Operations "*" -ResultSize 5000 - # Get all changes for this user from the Unified Audit Logs - [array]$UserChanges = Search-UnifiedAuditLog -UserIds $User -StartDate $Hawk.StartDate -EndDate $Hawk.EndDate -RecordType ExchangeAdmin -Operations "*" -ResultSize 5000 + # If there are any results push them to an output file + if ($UserChanges.Count -gt 0) { + Out-LogFile ("Found " + $UserChanges.Count + " changes made to this user") - # If there are any results, handle them - if ($UserChanges.Count -gt 0) { - Out-LogFile ("Found " + $UserChanges.Count + " changes made to this user") + # Get the user's output folder path + $UserFolder = Get-HawkUserPath -User $User - # Determine the user's output folder - $UserFolder = (Get-HawkUserPath -User $User) + # Write raw AuditData to files for verification/debugging + $RawJsonPath = Join-Path -Path $UserFolder -ChildPath "User_Changes_Raw.json" + $UserChanges | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawJsonPath - # Write raw AuditData JSON to a JSON file for verification - $RawJsonPath = Join-Path $UserFolder "User_Changes_Raw.json" - $UserChanges | Select-Object -ExpandProperty AuditData | Out-File $RawJsonPath + $RawTxtPath = Join-Path -Path $UserFolder -ChildPath "User_Changes_Raw.txt" + "User: $User" | Out-File -FilePath $RawTxtPath + $UserChanges | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawTxtPath -Append + "------------------------------------" | Out-File -FilePath $RawTxtPath -Append - # Also write raw data to a text file (similar to previous testing snippet) - $RawTxtPath = Join-Path $UserFolder "User_Changes_Raw.txt" - "User: $User" | Out-File $RawTxtPath - $UserChanges | Select-Object -ExpandProperty AuditData | Out-File $RawTxtPath -Append - "------------------------------------" | Out-File $RawTxtPath -Append + # Parse and format the changes using Get-SimpleUnifiedAuditLog + $ParsedChanges = $UserChanges | Get-SimpleUnifiedAuditLog - # Parse the results with the new Get-SimpleUnifiedAuditLog function - $ParsedChanges = $UserChanges | ForEach-Object { - $AuditDataJson = $_.AuditData - $AuditDataObj = $AuditDataJson | ConvertFrom-Json - $AuditDataObj - } | Get-SimpleUnifiedAuditLog - - # Output the parsed results + # Output the processed results + if ($ParsedChanges) { $ParsedChanges | Out-MultipleFileType -FilePrefix "Simple_User_Changes" -csv -json -User $User - $UserChanges | Out-MultipleFileType -FilePrefix "User_Changes" -csv -json -User $User - } - else { - Out-LogFile "No User Changes found." } + + # Output the raw changes + $UserChanges | Out-MultipleFileType -FilePrefix "User_Changes" -csv -json -User $User + } + else { + Out-LogFile "No User Changes found." } } +} \ No newline at end of file diff --git a/Hawk/internal/functions/Get-SimpleUnifiedAuditLog.ps1 b/Hawk/internal/functions/Get-SimpleUnifiedAuditLog.ps1 index 92db6ed..17d6d50 100644 --- a/Hawk/internal/functions/Get-SimpleUnifiedAuditLog.ps1 +++ b/Hawk/internal/functions/Get-SimpleUnifiedAuditLog.ps1 @@ -2,79 +2,50 @@ [CmdletBinding()] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSObject]$UALRecord + [PSObject]$Record ) Begin { - Write-Verbose "Starting Get-SimpleUnifiedAuditLog processing" $Results = @() } Process { - foreach ($record in $UALRecord) { - try { - Write-Verbose "Processing record with ID: $($record.Identity)" + try { + # Convert the AuditData JSON string to an object + $AuditData = $Record | Select-Object -ExpandProperty AuditData | ConvertFrom-Json - # The AuditData is a JSON string, so convert it - if ($record.AuditData) { - $AuditRecord = $record.AuditData | ConvertFrom-Json - - # Create result object with data from audit record - $obj = [PSCustomObject]@{ - Caller = if ($AuditRecord.UserId) { $AuditRecord.UserId } else { "***" } - Cmdlet = $AuditRecord.Operation - FullCommand = $AuditRecord.Operation - 'RunDate(UTC)' = $AuditRecord.CreationTime - ObjectModified = $AuditRecord.ObjectId - } + if ($AuditData) { + $obj = [PSCustomObject]@{ + Caller = $AuditData.UserId + Cmdlet = $AuditData.Operation + FullCommand = $AuditData.Operation + 'RunDate(UTC)' = $AuditData.CreationTime + ObjectModified = $AuditData.ObjectId + } - # Add parameters to FullCommand if they exist - if ($AuditRecord.Parameters) { - $paramString = foreach ($param in $AuditRecord.Parameters) { - # Handle different parameter value types appropriately - $value = if ($param.Value -match '\s') { - # If value contains spaces, quote it - "'$($param.Value)'" - } elseif ($param.Value -match '^(True|False)$') { - # If boolean, format with $ - "`$$($param.Value.ToLower())" - } else { - $param.Value - } - "-$($param.Name) $value" + # Add parameters to FullCommand + if ($AuditData.Parameters) { + $paramStrings = foreach ($param in $AuditData.Parameters) { + $value = switch -Regex ($param.Value) { + '^\s+|\s+$' { "'$($param.Value)'" } # Has leading/trailing spaces + '\s' { "'$($param.Value)'" } # Contains spaces + '^True$|^False$' { "`$$($param.Value.ToLower())" } # Boolean + default { $param.Value } } - $obj.FullCommand = "$($obj.Cmdlet) $($paramString -join ' ')" - } - - $Results += $obj - Write-Verbose "Successfully processed record" - } - else { - Write-Verbose "No AuditData found for record" - $Results += [PSCustomObject]@{ - Caller = "***" - Cmdlet = "Unknown" - FullCommand = "No audit data available" - 'RunDate(UTC)' = $null - ObjectModified = $null + "-$($param.Name) $value" } + $obj.FullCommand = "$($AuditData.Operation) $($paramStrings -join ' ')" } + + $Results += $obj } - catch { - Write-Verbose "Error processing record: $_" - $Results += [PSCustomObject]@{ - Caller = "***" - Cmdlet = "Error" - FullCommand = "Error processing audit record: $_" - 'RunDate(UTC)' = $null - ObjectModified = $null - } - } + } + catch { + Write-Verbose "Error processing record: $_" } } End { - Write-Verbose "Completed processing. Returning $($Results.Count) records" - return $Results + $Results } } \ No newline at end of file