functions/Tenant/Get-HawkTenantAdminEmailForwardingChange.ps1
Function Get-HawkTenantAdminEmailForwardingChange { <# .SYNOPSIS Retrieves audit log entries for email forwarding changes made within the tenant. .DESCRIPTION This function queries the Microsoft 365 Unified Audit Log for events related to email forwarding configuration changes (Set-Mailbox with forwarding parameters). It focuses on tracking when and by whom forwarding rules were added or modified, helping identify potential unauthorized data exfiltration attempts. Key points: - Monitors changes to both ForwardingAddress and ForwardingSMTPAddress settings - Resolves recipient information for ForwardingAddress values - Flags all forwarding changes for review as potential security concerns - Provides historical context for forwarding configuration changes .OUTPUTS File: Simple_Forwarding_Changes.csv/.json Path: \Tenant Description: Simplified view of forwarding configuration changes. File: Forwarding_Changes.csv/.json Path: \Tenant Description: Detailed audit log data for forwarding changes. File: Forwarding_Recipients.csv/.json Path: \Tenant Description: List of unique forwarding destinations configured. .EXAMPLE Get-HawkTenantAdminEmailForwardingChange Retrieves all email forwarding configuration changes from the audit logs within the specified search window. #> [CmdletBinding()] param() # Test the Exchange Online connection to ensure the environment is ready for operations. Test-EXOConnection # Log the execution of the function for audit and telemetry purposes. Send-AIEvent -Event "CmdRun" # Initialize timing variables for status updates $startTime = Get-Date $lastUpdate = $startTime # Log the start of the analysis process for email forwarding configuration changes. Out-LogFile "Analyzing email forwarding configuration changes from audit logs" -Action # Ensure the tenant-specific folder exists to store output files. If not, create it. $TenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant" if (-not (Test-Path -Path $TenantPath)) { New-Item -Path $TenantPath -ItemType Directory -Force | Out-Null } try { # Define both operations and broader search terms to cast a wider net. $searchCommand = @" Search-UnifiedAuditLog -RecordType ExchangeAdmin -Operations @( 'Set-Mailbox', 'Set-MailUser', 'Set-RemoteMailbox', 'Enable-RemoteMailbox' ) "@ # Fetch all specified operations from the audit log [array]$AllMailboxChanges = Get-AllUnifiedAuditLogEntry -UnifiedSearch $searchCommand # Log search completion time Out-LogFile "Unified Audit Log search completed" -Information Out-LogFile "Filtering results for forwarding changes..." -Action # Enhanced filtering to catch more types of forwarding changes [array]$ForwardingChanges = $AllMailboxChanges | Where-Object { $auditData = $_.AuditData | ConvertFrom-Json $parameters = $auditData.Parameters ($parameters | Where-Object { $_.Name -in @( 'ForwardingAddress', 'ForwardingSMTPAddress', 'ExternalEmailAddress', 'PrimarySmtpAddress', 'RedirectTo', # Added from other LLM suggestion 'DeliverToMailboxAndForward', # Corrected parameter name 'DeliverToAndForward' # Alternative parameter name ) -or # Check for parameter changes enabling forwarding ($_.Name -eq 'DeliverToMailboxAndForward' -and $_.Value -eq 'True') -or ($_.Name -eq 'DeliverToAndForward' -and $_.Value -eq 'True') }) } Out-LogFile "Completed filtering for forwarding changes" -Information if ($ForwardingChanges.Count -gt 0) { # Log the number of forwarding configuration changes found. Out-LogFile ("Found " + $ForwardingChanges.Count + " change(s) to user email forwarding") -Information # Write raw JSON data for detailed reference and potential troubleshooting. $RawJsonPath = Join-Path -Path $TenantPath -ChildPath "Forwarding_Changes_Raw.json" $ForwardingChanges | Select-Object -ExpandProperty AuditData | Out-File -FilePath $RawJsonPath # Parse the audit data into a simpler format for further processing and output. $ParsedChanges = $ForwardingChanges | Get-SimpleUnifiedAuditLog if ($ParsedChanges) { # Write the simplified data for quick analysis and review. $ParsedChanges | Out-MultipleFileType -FilePrefix "Simple_Forwarding_Changes" -csv -json -Notice # Write the full audit log data for comprehensive records. $ForwardingChanges | Out-MultipleFileType -FilePrefix "Forwarding_Changes" -csv -json -Notice # Initialize an array to store processed forwarding destination data. $ForwardingDestinations = @() Out-LogFile "Beginning detailed analysis of forwarding changes..." -Action foreach ($change in $ParsedChanges) { # Add a status update every 30 seconds $currentTime = Get-Date if (($currentTime - $lastUpdate).TotalSeconds -ge 30) { Out-LogFile "Processing forwarding changes... ($($ForwardingDestinations.Count) destinations found so far)" -Action $lastUpdate = $currentTime } $targetUser = $change.ObjectId # Process ForwardingSMTPAddress changes if detected in the audit log. if ($change.Parameters -match "ForwardingSMTPAddress") { $smtpAddress = ($change.Parameters | Select-String -Pattern "ForwardingSMTPAddress:\s*([^,]+)").Matches.Groups[1].Value if ($smtpAddress) { # Add the SMTP forwarding configuration to the destinations array. $ForwardingDestinations += [PSCustomObject]@{ UserModified = $targetUser TargetSMTPAddress = $smtpAddress.Split(":")[-1].Trim() # Remove "SMTP:" prefix if present. ChangeType = "SMTP Forwarding" ModifiedBy = $change.UserId ModifiedTime = $change.CreationTime } } } # Process ForwardingAddress changes if detected in the audit log. if ($change.Parameters -match "ForwardingAddress") { $forwardingAddress = ($change.Parameters | Select-String -Pattern "ForwardingAddress:\s*([^,]+)").Matches.Groups[1].Value if ($forwardingAddress) { try { # Attempt to resolve the recipient details from Exchange Online. $recipient = Get-EXORecipient $forwardingAddress -ErrorAction Stop # Determine the recipient's type and extract the appropriate address. $targetAddress = switch ($recipient.RecipientType) { "MailContact" { $recipient.ExternalEmailAddress.Split(":")[-1] } default { $recipient.PrimarySmtpAddress } } # Add the recipient forwarding configuration to the destinations array. $ForwardingDestinations += [PSCustomObject]@{ UserModified = $targetUser TargetSMTPAddress = $targetAddress ChangeType = "Recipient Forwarding" ModifiedBy = $change.UserId ModifiedTime = $change.CreationTime } } catch { # Log a warning if the recipient cannot be resolved. Out-LogFile "Unable to resolve forwarding recipient: $forwardingAddress" -Notice # Add an unresolved entry for transparency in the output. $ForwardingDestinations += [PSCustomObject]@{ UserModified = $targetUser TargetSMTPAddress = "UNRESOLVED:$forwardingAddress" ChangeType = "Recipient Forwarding (Unresolved)" ModifiedBy = $change.UserId ModifiedTime = $change.CreationTime } } } } } Out-LogFile "Completed processing forwarding changes" -Information if ($ForwardingDestinations.Count -gt 0) { # Log the total number of forwarding destinations detected. Out-LogFile ("Found " + $ForwardingDestinations.Count + " forwarding destinations configured") -Information # Write the forwarding destinations data to files for review. $ForwardingDestinations | Out-MultipleFileType -FilePrefix "Forwarding_Recipients" -csv -json -Notice # Log details about each forwarding destination for detailed auditing. foreach ($dest in $ForwardingDestinations) { Out-LogFile "Forwarding configured: $($dest.UserModified) -> $($dest.TargetSMTPAddress) ($($dest.ChangeType)) by $($dest.ModifiedBy) at $($dest.ModifiedTime)" -Notice } } } else { # Log a warning if the parsing of audit data fails. Out-LogFile "Error: Failed to parse forwarding change audit data" -Notice } } else { # Log a message if no forwarding changes are found in the logs. Out-LogFile "No forwarding changes found in filtered results" -Information Out-LogFile "Retrieved $($AllMailboxChanges.Count) total operations, but none involved forwarding changes" -Information } } catch { # Log an error if the analysis encounters an exception. Out-LogFile "Error analyzing email forwarding changes: $($_.Exception.Message)" -Notice Write-Error -ErrorRecord $_ -ErrorAction Continue } } |