Private/Audit/Invoke-EmailSecurityChecks.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Invoke-EmailSecurityChecks { [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$AuditData, [string]$OrgUnitPath = '/' ) $checkDefs = Get-AuditCategoryDefinitions -Category 'EmailSecurityChecks' $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($check in $checkDefs.checks) { $funcName = "Test-Fortification$($check.id -replace '-', '')" if (Get-Command $funcName -ErrorAction SilentlyContinue) { try { $finding = & $funcName -AuditData $AuditData -CheckDefinition $check -OrgUnitPath $OrgUnitPath if ($finding) { $findings.Add($finding) } } catch { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' ` -CurrentValue "Check failed: $_" -OrgUnitPath $OrgUnitPath)) } } else { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' ` -CurrentValue 'Check not yet implemented' -OrgUnitPath $OrgUnitPath)) } } return @($findings) } # ── EMAIL-001: SPF Record Validation ────────────────────────────────────────── function Test-FortificationEMAIL001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.DnsRecords -or $AuditData.DnsRecords.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No DNS records available for analysis' -OrgUnitPath $OrgUnitPath } $failedDomains = [System.Collections.Generic.List[string]]::new() $warnDomains = [System.Collections.Generic.List[string]]::new() $passedDomains = [System.Collections.Generic.List[string]]::new() foreach ($domainName in $AuditData.DnsRecords.Keys) { $dns = $AuditData.DnsRecords[$domainName] if ($dns.SPF.Valid -eq $true) { # Check for weak qualifiers if ($dns.SPF.Record -match '\+all') { $failedDomains.Add("$domainName (+all permits any sender)") } elseif ($dns.SPF.Record -match '\?all') { $warnDomains.Add("$domainName (?all neutral policy)") } else { $passedDomains.Add($domainName) } } else { $detail = if ($dns.SPF.Details) { " ($($dns.SPF.Details))" } else { '' } $failedDomains.Add("$domainName$detail") } } $totalDomains = $AuditData.DnsRecords.Count $status = if ($failedDomains.Count -gt 0) { 'FAIL' } elseif ($warnDomains.Count -gt 0) { 'WARN' } else { 'PASS' } $currentValue = "$($passedDomains.Count) of $totalDomains domain(s) have valid SPF records" if ($failedDomains.Count -gt 0) { $currentValue += "; $($failedDomains.Count) failed" } if ($warnDomains.Count -gt 0) { $currentValue += "; $($warnDomains.Count) with warnings" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath ` -Details @{ PassedDomains = @($passedDomains) FailedDomains = @($failedDomains) WarnDomains = @($warnDomains) TotalDomains = $totalDomains } } # ── EMAIL-002: DKIM Signing Enabled ─────────────────────────────────────────── function Test-FortificationEMAIL002 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.DnsRecords -or $AuditData.DnsRecords.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No DNS records available for analysis' -OrgUnitPath $OrgUnitPath } $failedDomains = [System.Collections.Generic.List[string]]::new() $passedDomains = [System.Collections.Generic.List[string]]::new() foreach ($domainName in $AuditData.DnsRecords.Keys) { $dns = $AuditData.DnsRecords[$domainName] if ($dns.DKIM.Valid -eq $true) { $passedDomains.Add($domainName) } else { $detail = if ($dns.DKIM.Details) { " ($($dns.DKIM.Details))" } else { '' } $failedDomains.Add("$domainName$detail") } } $totalDomains = $AuditData.DnsRecords.Count $status = if ($failedDomains.Count -gt 0) { 'FAIL' } else { 'PASS' } $currentValue = "$($passedDomains.Count) of $totalDomains domain(s) have valid DKIM records" if ($failedDomains.Count -gt 0) { $currentValue += "; $($failedDomains.Count) missing or invalid" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath ` -Details @{ PassedDomains = @($passedDomains) FailedDomains = @($failedDomains) TotalDomains = $totalDomains } } # ── EMAIL-003: DMARC Policy Audit ───────────────────────────────────────────── function Test-FortificationEMAIL003 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.DnsRecords -or $AuditData.DnsRecords.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No DNS records available for analysis' -OrgUnitPath $OrgUnitPath } $failedDomains = [System.Collections.Generic.List[string]]::new() $warnDomains = [System.Collections.Generic.List[string]]::new() $passedDomains = [System.Collections.Generic.List[string]]::new() foreach ($domainName in $AuditData.DnsRecords.Keys) { $dns = $AuditData.DnsRecords[$domainName] if ($dns.DMARC.Valid -eq $true) { switch ($dns.DMARC.Policy) { 'reject' { $passedDomains.Add($domainName) } 'quarantine' { $passedDomains.Add("$domainName (quarantine)") } 'none' { $warnDomains.Add("$domainName (policy=none, monitoring only)") } default { $warnDomains.Add("$domainName (unknown policy)") } } } else { $detail = if ($dns.DMARC.Details) { " ($($dns.DMARC.Details))" } else { '' } $failedDomains.Add("$domainName$detail") } } $totalDomains = $AuditData.DnsRecords.Count $status = if ($failedDomains.Count -gt 0) { 'FAIL' } elseif ($warnDomains.Count -gt 0) { 'WARN' } else { 'PASS' } $currentValue = "$($passedDomains.Count) of $totalDomains domain(s) have enforcing DMARC policy" if ($failedDomains.Count -gt 0) { $currentValue += "; $($failedDomains.Count) missing DMARC" } if ($warnDomains.Count -gt 0) { $currentValue += "; $($warnDomains.Count) with non-enforcing policy" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath ` -Details @{ PassedDomains = @($passedDomains) FailedDomains = @($failedDomains) WarnDomains = @($warnDomains) TotalDomains = $totalDomains } } # ── EMAIL-004: MTA-STS Policy ───────────────────────────────────────────────── function Test-FortificationEMAIL004 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.DnsRecords -or $AuditData.DnsRecords.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No DNS records available for analysis' -OrgUnitPath $OrgUnitPath } $failedDomains = [System.Collections.Generic.List[string]]::new() $passedDomains = [System.Collections.Generic.List[string]]::new() foreach ($domainName in $AuditData.DnsRecords.Keys) { $dns = $AuditData.DnsRecords[$domainName] if ($dns.MTASTS.Valid -eq $true) { $passedDomains.Add($domainName) } else { $detail = if ($dns.MTASTS.Details) { " ($($dns.MTASTS.Details))" } else { '' } $failedDomains.Add("$domainName$detail") } } $totalDomains = $AuditData.DnsRecords.Count $status = if ($failedDomains.Count -gt 0) { 'WARN' } else { 'PASS' } $currentValue = "$($passedDomains.Count) of $totalDomains domain(s) have MTA-STS configured" if ($failedDomains.Count -gt 0) { $currentValue += "; $($failedDomains.Count) without MTA-STS" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath ` -Details @{ PassedDomains = @($passedDomains) FailedDomains = @($failedDomains) TotalDomains = $totalDomains } } # ── EMAIL-005: TLS Enforcement ──────────────────────────────────────────────── function Test-FortificationEMAIL005 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'TLS enforcement settings require manual verification. Verify in Admin Console > Apps > Gmail > Compliance > Secure transport (TLS) compliance' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'TLS compliance settings are OU-level policies not fully available via API' } } # ── EMAIL-006: Email Allowlist/Blocklist Review ─────────────────────────────── function Test-FortificationEMAIL006 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Email allowlist and blocklist entries require manual review. Verify in Admin Console > Apps > Gmail > Spam, phishing and malware' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Allowlist/blocklist configuration is an OU-level policy not fully available via API' } } # ── EMAIL-007: Inbound Gateway Configuration ───────────────────────────────── function Test-FortificationEMAIL007 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Inbound gateway configuration requires manual verification. Verify in Admin Console > Apps > Gmail > Spam, phishing and malware > Inbound gateway' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Inbound gateway settings are OU-level policies not fully available via API' } } # ── EMAIL-008: Email Routing Rules Audit ────────────────────────────────────── function Test-FortificationEMAIL008 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Email routing rules require manual review. Verify in Admin Console > Apps > Gmail > Routing for unauthorized or suspicious entries' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Routing rule configuration is an OU-level policy not fully available via API' } } # ── EMAIL-009: Auto-Forwarding Policy ───────────────────────────────────────── function Test-FortificationEMAIL009 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.GmailSettings -or $AuditData.GmailSettings.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No Gmail settings data available' -OrgUnitPath $OrgUnitPath } $usersWithForwarding = [System.Collections.Generic.List[PSCustomObject]]::new() $totalUsers = 0 foreach ($userEmail in $AuditData.GmailSettings.Keys) { $settings = $AuditData.GmailSettings[$userEmail] $totalUsers++ if ($settings.autoForwarding -and $settings.autoForwarding.enabled -eq $true) { $usersWithForwarding.Add([PSCustomObject]@{ User = $userEmail ForwardingAddress = $settings.autoForwarding.emailAddress }) } } if ($usersWithForwarding.Count -gt 0) { $forwardingDetails = @($usersWithForwarding | ForEach-Object { "$($_.User) -> $($_.ForwardingAddress)" }) return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "$($usersWithForwarding.Count) of $totalUsers user(s) have auto-forwarding enabled" ` -OrgUnitPath $OrgUnitPath ` -Details @{ UsersWithForwarding = $forwardingDetails TotalUsersChecked = $totalUsers } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "No users have auto-forwarding enabled ($totalUsers users checked)" ` -OrgUnitPath $OrgUnitPath ` -Details @{ TotalUsersChecked = $totalUsers } } # ── EMAIL-010: Delegate Access Settings ─────────────────────────────────────── function Test-FortificationEMAIL010 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.GmailSettings -or $AuditData.GmailSettings.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No Gmail settings data available' -OrgUnitPath $OrgUnitPath } $usersWithDelegates = [System.Collections.Generic.List[PSCustomObject]]::new() $totalUsers = 0 foreach ($userEmail in $AuditData.GmailSettings.Keys) { $settings = $AuditData.GmailSettings[$userEmail] $totalUsers++ # Check for sendAs aliases that are not the user's own address if ($settings.sendAs) { $aliases = @($settings.sendAs | Where-Object { $_.sendAsEmail -and $_.sendAsEmail -ne $userEmail }) if ($aliases.Count -gt 0) { $aliasAddresses = @($aliases | ForEach-Object { $_.sendAsEmail }) $usersWithDelegates.Add([PSCustomObject]@{ User = $userEmail Aliases = $aliasAddresses }) } } } if ($usersWithDelegates.Count -gt 0) { $delegateDetails = @($usersWithDelegates | ForEach-Object { "$($_.User): $($_.Aliases -join ', ')" }) return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "$($usersWithDelegates.Count) of $totalUsers user(s) have send-as aliases configured. Review for unauthorized delegates" ` -OrgUnitPath $OrgUnitPath ` -Details @{ UsersWithDelegates = $delegateDetails TotalUsersChecked = $totalUsers } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "No send-as aliases found ($totalUsers users checked)" ` -OrgUnitPath $OrgUnitPath ` -Details @{ TotalUsersChecked = $totalUsers } } # ── EMAIL-011: POP/IMAP Access Settings ─────────────────────────────────────── function Test-FortificationEMAIL011 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.GmailSettings -or $AuditData.GmailSettings.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No Gmail settings data available' -OrgUnitPath $OrgUnitPath } $usersWithImap = [System.Collections.Generic.List[string]]::new() $usersWithPop = [System.Collections.Generic.List[string]]::new() $totalUsers = 0 foreach ($userEmail in $AuditData.GmailSettings.Keys) { $settings = $AuditData.GmailSettings[$userEmail] $totalUsers++ if ($settings.imap -and $settings.imap.enabled -eq $true) { $usersWithImap.Add($userEmail) } if ($settings.pop -and $settings.pop.accessWindow -and $settings.pop.accessWindow -ne 'disabled') { $usersWithPop.Add($userEmail) } } $totalLegacy = $usersWithImap.Count + $usersWithPop.Count if ($totalLegacy -gt 0) { $currentValue = '' if ($usersWithImap.Count -gt 0) { $currentValue += "$($usersWithImap.Count) user(s) with IMAP enabled" } if ($usersWithPop.Count -gt 0) { if ($currentValue) { $currentValue += '; ' } $currentValue += "$($usersWithPop.Count) user(s) with POP enabled" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath ` -Details @{ UsersWithIMAP = @($usersWithImap) UsersWithPOP = @($usersWithPop) TotalUsersChecked = $totalUsers } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "POP and IMAP disabled for all $totalUsers user(s) checked" ` -OrgUnitPath $OrgUnitPath ` -Details @{ TotalUsersChecked = $totalUsers } } # ── EMAIL-012: Spam and Phishing Filter Settings ────────────────────────────── function Test-FortificationEMAIL012 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Spam and phishing filter settings require manual verification. Verify in Admin Console > Apps > Gmail > Spam, phishing and malware' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Spam/phishing filter configuration is an OU-level policy not fully available via API' } } # ── EMAIL-013: Enhanced Pre-Delivery Message Scanning ───────────────────────── function Test-FortificationEMAIL013 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Enhanced pre-delivery message scanning requires manual verification. Verify in Admin Console > Apps > Gmail > Spam, phishing and malware' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Pre-delivery scanning is an OU-level policy not fully available via API' } } # ── EMAIL-014: External Recipient Warning ───────────────────────────────────── function Test-FortificationEMAIL014 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'External recipient warning setting requires manual verification. Verify in Admin Console > Apps > Gmail > End User Access' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'External recipient warning is an OU-level policy not fully available via API' } } # ── EMAIL-015: Attachment Safety Settings ───────────────────────────────────── function Test-FortificationEMAIL015 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Attachment safety settings require manual verification. Verify in Admin Console > Apps > Gmail > Safety > Attachments' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Attachment safety configuration is an OU-level policy not fully available via API' } } # ── EMAIL-016: Links and External Images Protection ─────────────────────────── function Test-FortificationEMAIL016 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Links and external images protection requires manual verification. Verify in Admin Console > Apps > Gmail > Safety > Links and external images' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Link/image protection configuration is an OU-level policy not fully available via API' } } # ── EMAIL-017: Spoofing and Authentication Protection ───────────────────────── function Test-FortificationEMAIL017 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Spoofing and authentication protection requires manual verification. Verify in Admin Console > Apps > Gmail > Safety > Spoofing and authentication' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Spoofing protection configuration is an OU-level policy not fully available via API' } } # ── EMAIL-018: Compliance Rules Audit ───────────────────────────────────────── function Test-FortificationEMAIL018 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Content compliance rules require manual review. Verify in Admin Console > Apps > Gmail > Compliance > Content compliance' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Content compliance configuration is an OU-level policy not fully available via API' } } # ── EMAIL-019: DLP Rules Configuration ──────────────────────────────────────── function Test-FortificationEMAIL019 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'DLP rules configuration requires manual review. Verify in Admin Console > Security > Data protection > Manage rules' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'DLP rule configuration is an OU-level policy not fully available via API' } } # ── EMAIL-020: Gmail Confidential Mode ──────────────────────────────────────── function Test-FortificationEMAIL020 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Gmail confidential mode settings require manual verification. Verify in Admin Console > Apps > Gmail > End User Access' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Confidential mode configuration is an OU-level policy not fully available via API' } } # ── EMAIL-021: S/MIME Settings ──────────────────────────────────────────────── function Test-FortificationEMAIL021 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'S/MIME settings require manual verification. Verify in Admin Console > Apps > Gmail > End User Access > S/MIME' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'S/MIME configuration is an OU-level policy not fully available via API' } } # ── EMAIL-022: Mail Forwarding Rule Enumeration ─────────────────────────────── function Test-FortificationEMAIL022 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') if (-not $AuditData.GmailSettings -or $AuditData.GmailSettings.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No Gmail settings data available' -OrgUnitPath $OrgUnitPath } $allForwardingRules = [System.Collections.Generic.List[PSCustomObject]]::new() $totalUsers = 0 foreach ($userEmail in $AuditData.GmailSettings.Keys) { $settings = $AuditData.GmailSettings[$userEmail] $totalUsers++ # Check auto-forwarding if ($settings.autoForwarding -and $settings.autoForwarding.enabled -eq $true) { $allForwardingRules.Add([PSCustomObject]@{ User = $userEmail Type = 'AutoForwarding' ForwardingAddress = $settings.autoForwarding.emailAddress }) } # Check filters with forwarding actions if ($settings.filters) { foreach ($filter in $settings.filters) { if ($filter.action -and $filter.action.forward) { $allForwardingRules.Add([PSCustomObject]@{ User = $userEmail Type = 'FilterForwarding' ForwardingAddress = $filter.action.forward }) } } } # Check sendAs aliases with replyTo pointing externally if ($settings.sendAs) { foreach ($alias in $settings.sendAs) { if ($alias.sendAsEmail -and $alias.sendAsEmail -ne $userEmail) { $allForwardingRules.Add([PSCustomObject]@{ User = $userEmail Type = 'SendAsAlias' ForwardingAddress = $alias.sendAsEmail }) } } } # Check forwarding addresses (registered but may not be active) if ($settings.forwardingAddresses) { foreach ($fwd in $settings.forwardingAddresses) { if ($fwd.forwardingEmail) { $allForwardingRules.Add([PSCustomObject]@{ User = $userEmail Type = 'RegisteredForwarding' ForwardingAddress = $fwd.forwardingEmail }) } } } } if ($allForwardingRules.Count -gt 0) { $ruleDetails = @($allForwardingRules | ForEach-Object { "$($_.User) [$($_.Type)] -> $($_.ForwardingAddress)" }) $autoCount = @($allForwardingRules | Where-Object { $_.Type -eq 'AutoForwarding' }).Count $filterCount = @($allForwardingRules | Where-Object { $_.Type -eq 'FilterForwarding' }).Count $aliasCount = @($allForwardingRules | Where-Object { $_.Type -eq 'SendAsAlias' }).Count $registeredCount = @($allForwardingRules | Where-Object { $_.Type -eq 'RegisteredForwarding' }).Count $summary = "$($allForwardingRules.Count) forwarding rule(s) found across $totalUsers user(s)" $breakdown = @() if ($autoCount -gt 0) { $breakdown += "$autoCount auto-forwarding" } if ($filterCount -gt 0) { $breakdown += "$filterCount filter-based" } if ($aliasCount -gt 0) { $breakdown += "$aliasCount send-as aliases" } if ($registeredCount -gt 0) { $breakdown += "$registeredCount registered addresses" } if ($breakdown.Count -gt 0) { $summary += " ($($breakdown -join '; '))" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue $summary -OrgUnitPath $OrgUnitPath ` -Details @{ ForwardingRules = $ruleDetails AutoForwarding = $autoCount FilterForwarding = $filterCount SendAsAliases = $aliasCount RegisteredAddresses = $registeredCount TotalUsersChecked = $totalUsers } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "No forwarding rules found ($totalUsers users checked)" ` -OrgUnitPath $OrgUnitPath ` -Details @{ TotalUsersChecked = $totalUsers } } |