Src/Private/Get-AbrExoAntiSpam.ps1

function Get-AbrExoAntiSpam {
    <#
    .SYNOPSIS
    Documents Exchange Online anti-spam policy configuration with ACSC E8 and CIS compliance assessments.
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting Exchange Online Anti-Spam configuration for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'AntiSpam'
    }

    process {
        Section -Style Heading2 'Anti-Spam Policies' {
            Paragraph "The following section documents the inbound and outbound anti-spam policies configured for tenant $TenantId."
            BlankLine

            #region Inbound Anti-Spam
            try {
                Write-Host " - Retrieving inbound anti-spam policies..."
                $InboundPolicies = Get-HostedContentFilterPolicy -ErrorAction Stop | Sort-Object IsDefault -Descending

                $InboundSpamPolicyCount         = @($InboundPolicies).Count
                $DefaultInboundPolicy           = $InboundPolicies | Where-Object { $_.IsDefault } | Select-Object -First 1
                if (-not $DefaultInboundPolicy) { $DefaultInboundPolicy = $InboundPolicies | Select-Object -First 1 }
                $HighConfidenceSpamAction        = $DefaultInboundPolicy.HighConfidenceSpamAction
                $HighConfidenceSpamActionOk      = $HighConfidenceSpamAction -in @('Quarantine', 'Delete')
                $PhishSpamAction                 = $DefaultInboundPolicy.PhishSpamAction
                $PhishSpamActionOk               = $PhishSpamAction -in @('Quarantine', 'MoveToJmf', 'Delete')
                $BulkThresholdRaw                = $DefaultInboundPolicy.BulkThreshold
                $BulkThreshold                   = if ($null -ne $BulkThresholdRaw -and "$BulkThresholdRaw" -match '^\d+$') { [int]$BulkThresholdRaw } else { 7 }

                if ($InboundPolicies) {
                    Section -Style Heading3 'Inbound Anti-Spam Policies' {
                        Paragraph "The following $InboundSpamPolicyCount inbound anti-spam policy/policies are configured in tenant $TenantId."
                        BlankLine

                        $InboundObj = [System.Collections.ArrayList]::new()
                        foreach ($Policy in $InboundPolicies) {
                            $inboundInObj = [ordered] @{
                                'Policy Name'                   = $Policy.Name
                                'Default Policy'                = $Policy.IsDefault
                                'Spam Action'                   = $Policy.SpamAction
                                'HC Spam Action'                = $Policy.HighConfidenceSpamAction
                                'Phish Action'                  = $Policy.PhishSpamAction
                                'HC Phish Action'               = $Policy.HighConfidencePhishAction
                                'BCL Threshold'                 = if ($null -ne $Policy.BulkThreshold) { $Policy.BulkThreshold } else { '--' }
                                'Bulk Action'                   = $Policy.BulkSpamAction
                                'ZAP Spam'                      = $Policy.SpamZapEnabled
                                'ZAP Phish'                     = $Policy.PhishZapEnabled
                            }
                            $InboundObj.Add([pscustomobject](ConvertTo-HashToYN $inboundInObj)) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.ExchangeOnline.AntiSpam) {
                                $null = ($InboundObj | Where-Object {
                                    $_.'HC Spam Action' -notin @('Quarantine', 'Delete')
                                } | Set-Style -Style Critical | Out-Null)
                                $null = ($InboundObj | Where-Object {
                                    $_.'BCL Threshold' -ne '--' -and [int]$_.'BCL Threshold' -gt 6
                                } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $InboundTableParams = @{
                            Name         = "Inbound Anti-Spam Policies - $TenantId"
                            List         = $false
                            ColumnWidths = 20, 8, 10, 12, 10, 12, 8, 10, 5, 5
                        }
                        if ($Report.ShowTableCaptions) { $InboundTableParams['Caption'] = "- $($InboundTableParams.Name)" }
                        $InboundObj | Table @InboundTableParams

                        $script:ExcelSheets['Anti-Spam Inbound'] = $InboundObj
                    }
                }

                # Retrieve filter rules (policy assignments)
                $FilterRules = Get-HostedContentFilterRule -ErrorAction SilentlyContinue | Sort-Object Priority
                if ($InfoLevel.AntiSpam -ge 2 -and $FilterRules) {
                    Section -Style Heading3 'Anti-Spam Policy Assignments' {
                        Paragraph "The following anti-spam policy assignments determine which users/groups receive each policy."
                        BlankLine

                        $RuleObj = [System.Collections.ArrayList]::new()
                        foreach ($Rule in $FilterRules) {
                            $ruleInObj = [ordered] @{
                                'Rule Name'          = $Rule.Name
                                'Priority'           = $Rule.Priority
                                'State'              = $Rule.State
                                'Policy'             = $Rule.HostedContentFilterPolicy
                                'Applied To'         = (@($Rule.SentTo) + @($Rule.SentToMemberOf) + @($Rule.RecipientDomainIs)) -join ', '
                                'Except'             = (@($Rule.ExceptIfSentTo) + @($Rule.ExceptIfSentToMemberOf)) -join ', '
                            }
                            $RuleObj.Add([pscustomobject]$ruleInObj) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.ExchangeOnline.AntiSpam) {
                                $null = ($RuleObj | Where-Object { $_.State -eq 'Disabled' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $RuleTableParams = @{ Name = "Anti-Spam Policy Assignments - $TenantId"; List = $false; ColumnWidths = 20, 8, 8, 20, 24, 20 }
                        if ($Report.ShowTableCaptions) { $RuleTableParams['Caption'] = "- $($RuleTableParams.Name)" }
                        $RuleObj | Table @RuleTableParams
                    }
                }
            } catch {
                Write-ExoError 'AntiSpam' "Unable to retrieve inbound anti-spam policies: $($_.Exception.Message)"
                Paragraph "Unable to retrieve inbound anti-spam policy data."
            }
            #endregion

            #region Outbound Anti-Spam
            try {
                Write-Host " - Retrieving outbound anti-spam policies..."
                $OutboundPolicies = Get-HostedOutboundSpamFilterPolicy -ErrorAction Stop

                $OutboundSpamPolicyCount = @($OutboundPolicies).Count

                if ($OutboundPolicies) {
                    Section -Style Heading3 'Outbound Anti-Spam Policies' {
                        Paragraph "The following $OutboundSpamPolicyCount outbound anti-spam policy/policies are configured for tenant $TenantId."
                        BlankLine

                        $OutboundObj = [System.Collections.ArrayList]::new()
                        foreach ($Policy in $OutboundPolicies) {
                            $outInObj = [ordered] @{
                                'Policy Name'                        = $Policy.Name
                                'Default Policy'                     = $Policy.IsDefault
                                'Action on Limit Exceeded'           = $Policy.ActionWhenThresholdReached
                                'Recipient Limit (external/hr)'      = $Policy.RecipientLimitExternalPerHour
                                'Recipient Limit (internal/hr)'      = $Policy.RecipientLimitInternalPerHour
                                'Recipient Limit (all/day)'          = $Policy.RecipientLimitPerDay
                                'Auto-Forward Mode'                  = $Policy.AutoForwardingMode
                                'Notify Sender on Limit'             = $Policy.NotifyOutboundSpam
                                'BCC Notification Address'           = if ($Policy.BccSuspiciousOutboundMail) { $Policy.BccSuspiciousOutboundAdditionalRecipients -join ', ' } else { 'None' }
                            }
                            $OutboundObj.Add([pscustomobject](ConvertTo-HashToYN $outInObj)) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.ExchangeOnline.AntiSpam) {
                                # Auto-forwarding should be Off or Automatic (not On)
                                $null = ($OutboundObj | Where-Object { $_.'Auto-Forward Mode' -eq 'On' } | Set-Style -Style Critical | Out-Null)
                                $null = ($OutboundObj | Where-Object { $_.'Auto-Forward Mode' -eq 'Automatic' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $OutboundTableParams = @{ Name = "Outbound Anti-Spam Policies - $TenantId"; List = $false; ColumnWidths = 14, 7, 13, 12, 12, 12, 12, 10, 8 }
                        if ($Report.ShowTableCaptions) { $OutboundTableParams['Caption'] = "- $($OutboundTableParams.Name)" }
                        $OutboundObj | Table @OutboundTableParams

                        $script:ExcelSheets['Anti-Spam Outbound'] = $OutboundObj
                    }
                }
            } catch {
                Write-ExoError 'AntiSpam' "Unable to retrieve outbound anti-spam policies: $($_.Exception.Message)"
            }
            #endregion


            BlankLine
            Paragraph "ACSC Essential Eight Assessment"
            BlankLine
            $e8Vars = @{
                InboundSpamPolicyCount     = $InboundSpamPolicyCount
                HighConfidenceSpamAction    = $HighConfidenceSpamAction
                HighConfidenceSpamActionOk  = $HighConfidenceSpamActionOk
                PhishSpamAction             = $PhishSpamAction
                PhishSpamActionOk           = $PhishSpamActionOk
                BulkThreshold               = $BulkThreshold
                OutboundSpamPolicyCount     = $OutboundSpamPolicyCount
            }
            $e8Checks = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoE8Checks 'AntiSpam') -Framework 'E8' -CallerVariables $e8Vars
            New-AbrExoE8AssessmentTable -Checks $e8Checks -Name 'Anti-Spam' -TenantId $TenantId
            if ($e8Checks) {
                foreach ($row in $e8Checks) {
                    $null = $script:E8AllChecks.Add([pscustomobject]@{
                        Section = 'Anti-Spam'
                        ML      = $row.ML
                        Control = $row.Control
                        Status  = $row.Status
                        Detail  = $row.Detail
                    })
                }
            }

            BlankLine
            Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment"
            BlankLine
            $cisVars = @{
                InboundSpamPolicyCount     = $InboundSpamPolicyCount
                BulkThreshold              = $BulkThreshold
                HighConfidenceSpamAction   = $HighConfidenceSpamAction
                HighConfidenceSpamActionOk = $HighConfidenceSpamActionOk
            }
            $cisChecks = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoCISChecks 'AntiSpam') -Framework 'CIS' -CallerVariables $cisVars
            New-AbrExoCISAssessmentTable -Checks $cisChecks -Name 'Anti-Spam' -TenantId $TenantId
            if ($cisChecks) {
                foreach ($row in $cisChecks) {
                    $null = $script:CISAllChecks.Add([pscustomobject]@{
                        Section    = 'Anti-Spam'
                        CISControl = $row.CISControl
                        Level      = $row.Level
                        Status     = $row.Status
                        Detail     = $row.Detail
                    })
                }
            }
        }

    }



    end {

        Show-AbrDebugExecutionTime -End -TitleMessage 'AntiSpam'

    }

}