SHELL/2.4.2.ps1

$CheckId = "2.4.2"
$Title = "Ensure Priority accounts have 'Strict protection' presets applied"
$Level = "L1"
$BenchmarkType = "Automated"
$AuditCommands = [pscustomobject]@{
    EOPStrictPreset = "Get-EOPProtectionPolicyRule | Where-Object { Name contains 'Strict Preset Security Policy' }"
    ATPStrictPreset = "Get-ATPProtectionPolicyRule | Where-Object { Name contains 'Strict Preset Security Policy' }"
}

function Get-AvailableCommandName {
    param(
        [string[]]$Names
    )

    foreach ($Name in $Names) {
        if (Get-Command -Name $Name -ErrorAction SilentlyContinue) {
            return $Name
        }
    }

    return $null
}

function Convert-ToAuditText {
    param(
        [AllowNull()]
        [object]$InputObject
    )

    if ($null -eq $InputObject) {
        return ""
    }

    $JsonText = ""
    try {
        $JsonText = $InputObject | ConvertTo-Json -Depth 12 -Compress
    }
    catch {
        $JsonText = ""
    }

    $RawText = ""
    try {
        $RawText = $InputObject | Out-String
    }
    catch {
        $RawText = ""
    }

    return "$JsonText $RawText".ToLowerInvariant().Replace("e-mail", "email")
}

function Get-ScopeValues {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Rule
    )

    $ScopeProperties = @(
        "SentTo",
        "SentToMemberOf",
        "Users",
        "UserTags",
        "RecipientTags",
        "Recipients",
        "RecipientDomainIs"
    )

    $Values = @()
    foreach ($Name in $ScopeProperties) {
        if ($null -ne $Rule.PSObject.Properties[$Name]) {
            $Current = @($Rule.$Name | ForEach-Object { ([string]$_).Trim() } | Where-Object {
                -not [string]::IsNullOrWhiteSpace($_) -and $_ -ne "{}"
            })
            $Values += $Current
        }
    }

    return @($Values | Select-Object -Unique)
}

function Get-StrictPresetEvidence {
    param(
        [Parameter(Mandatory = $true)]
        [string]$CommandName,
        [Parameter(Mandatory = $true)]
        [string]$Category
    )

    $AllRules = @(& $CommandName)
    $StrictRules = @($AllRules | Where-Object {
        $Text = Convert-ToAuditText -InputObject $_
        $Text.Contains("strict preset security policy")
    })

    $RuleDetails = foreach ($Rule in $StrictRules) {
        $ScopeValues = Get-ScopeValues -Rule $Rule
        [pscustomobject]@{
            Identity = [string]$Rule.Identity
            Name = [string]$Rule.Name
            ScopeValues = @($ScopeValues)
            ScopeCount = @($ScopeValues).Count
            IncludesPriorityKeyword = (@($ScopeValues | Where-Object { ([string]$_).ToLowerInvariant().Contains("priority") })).Count -gt 0
        }
    }

    [pscustomobject]@{
        Category = $Category
        CommandUsed = $CommandName
        RuleCount = @($AllRules).Count
        StrictRuleCount = @($StrictRules).Count
        Rules = @($RuleDetails)
    }
}

try {
    $FailureReasons = [System.Collections.Generic.List[string]]::new()
    $ManualReasons = [System.Collections.Generic.List[string]]::new()

    $EopEvidence = [ordered]@{
        Assessable = $false
        Result = $null
        Details = $null
    }

    $AtpEvidence = [ordered]@{
        Assessable = $false
        Result = $null
        Details = $null
    }

    # CIS v6.0.1 2.4.2 audit is UI-based; use cmdlets as best-effort verification.
    $EopCmd = Get-AvailableCommandName -Names @("Get-EOPProtectionPolicyRule")
    if ($EopCmd) {
        $EopEvidence.Assessable = $true
        $EopDetails = Get-StrictPresetEvidence -CommandName $EopCmd -Category "Exchange Online Protection"
        $EopEvidence.Details = $EopDetails

        if ($EopDetails.StrictRuleCount -eq 0) {
            $EopEvidence.Result = $false
            $FailureReasons.Add("Strict Preset Security Policy rule was not found for Exchange Online Protection.")
        }
        else {
            $HasScope = @($EopDetails.Rules | Where-Object { $_.ScopeCount -gt 0 }).Count -gt 0
            $EopEvidence.Result = $HasScope
            if (-not $HasScope) {
                $FailureReasons.Add("EOP strict preset policy exists but no specific recipients/groups were detected in scope.")
            }
        }
    }
    else {
        $ManualReasons.Add("Get-EOPProtectionPolicyRule cmdlet is unavailable in the current session.")
    }

    $AtpCmd = Get-AvailableCommandName -Names @("Get-ATPProtectionPolicyRule")
    if ($AtpCmd) {
        $AtpEvidence.Assessable = $true
        $AtpDetails = Get-StrictPresetEvidence -CommandName $AtpCmd -Category "Defender for Office 365"
        $AtpEvidence.Details = $AtpDetails

        if ($AtpDetails.StrictRuleCount -eq 0) {
            $AtpEvidence.Result = $false
            $FailureReasons.Add("Strict Preset Security Policy rule was not found for Defender for Office 365 protection.")
        }
        else {
            $HasScope = @($AtpDetails.Rules | Where-Object { $_.ScopeCount -gt 0 }).Count -gt 0
            $AtpEvidence.Result = $HasScope
            if (-not $HasScope) {
                $FailureReasons.Add("Defender strict preset policy exists but no specific recipients/groups were detected in scope.")
            }
        }
    }
    else {
        $ManualReasons.Add("Get-ATPProtectionPolicyRule cmdlet is unavailable in the current session.")
    }

    # Mapping to org-defined priority accounts/groups may still require manual validation.
    if ($FailureReasons.Count -eq 0 -and $ManualReasons.Count -eq 0) {
        $AllScopeValues = @()
        if ($EopEvidence.Details) {
            $AllScopeValues += @($EopEvidence.Details.Rules | ForEach-Object { $_.ScopeValues })
        }
        if ($AtpEvidence.Details) {
            $AllScopeValues += @($AtpEvidence.Details.Rules | ForEach-Object { $_.ScopeValues })
        }

        $AllScopeValues = @($AllScopeValues | Select-Object -Unique)
        $HasPriorityNamedScope = @($AllScopeValues | Where-Object {
            ([string]$_).ToLowerInvariant().Contains("priority")
        }).Count -gt 0

        if (-not $HasPriorityNamedScope) {
            $ManualReasons.Add("Strict preset policies were found, but automatic validation could not confirm they map to the organization's defined priority accounts/groups.")
        }
    }

    $Status = "PASS"
    $Pass = $true
    $ErrorMessage = $null

    if ($FailureReasons.Count -gt 0) {
        $Status = "FAIL"
        $Pass = $false
        $ErrorMessage = ($FailureReasons -join " ")
    }
    elseif ($ManualReasons.Count -gt 0) {
        $Status = "MANUAL_REVIEW"
        $Pass = $null
        $ErrorMessage = "Manual review required: $($ManualReasons -join " ")"
    }

    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = $Status
        Pass = $Pass
        Evidence = [pscustomobject]@{
            AuditCommands = $AuditCommands
            AuditUiReference = @(
                "Microsoft 365 Defender > E-mail & collaboration > Policies & rules > Threat policies",
                "Verify Strict Preset Security Policy appears in Anti-phishing, Anti-spam, Anti-malware, Safe Attachments, and Safe Links",
                "Verify strict preset includes the organization's priority accounts/groups"
            )
            EOPCheck = [pscustomobject]$EopEvidence
            DefenderCheck = [pscustomobject]$AtpEvidence
            ManualReviewReasons = @($ManualReasons)
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = $ErrorMessage
        Timestamp = Get-Date
    }
}
catch {
    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = "ERROR"
        Pass = $null
        Evidence = [pscustomobject]@{
            AuditCommands = $AuditCommands
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = $_.Exception.Message
        Timestamp = Get-Date
    }
}