SHELL/2.4.1.ps1

$CheckId = "2.4.1"
$Title = "Ensure Priority account protection is enabled and configured"
$Level = "L1"
$BenchmarkType = "Automated"
$AuditCommands = [pscustomobject]@{
    TenantSettings = "Get-EmailTenantSettings"
    UserTags       = "Get-UserTag (or Get-UserTags)"
    AlertPolicies  = "Get-ProtectionAlert (fallback: Get-AlertPolicy or Get-ActivityAlert)"
}

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

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

    return $null
}

function Get-FirstPropertyValue {
    param(
        [Parameter(Mandatory = $true)]
        [object]$InputObject,
        [Parameter(Mandatory = $true)]
        [string[]]$PropertyNames
    )

    if ($null -eq $InputObject -or $null -eq $InputObject.PSObject) {
        return $null
    }

    foreach ($PropertyName in $PropertyNames) {
        $Property = $InputObject.PSObject.Properties[$PropertyName]
        if ($null -ne $Property) {
            return $Property.Value
        }
    }

    return $null
}

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

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

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

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

    return "$Json $Text".ToLowerInvariant().Replace("e-mail", "email")
}

function Test-RequiredAlert {
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$Alerts,
        [Parameter(Mandatory = $true)]
        [string]$ActivityText
    )

    foreach ($Alert in @($Alerts)) {
        $SearchText = Convert-ToAuditText -InputObject $Alert
        $ActivityOk = $SearchText.Contains($ActivityText.ToLowerInvariant())

        if (-not $ActivityOk) {
            continue
        }

        $SeverityValue = [string](Get-FirstPropertyValue -InputObject $Alert -PropertyNames @("Severity", "AlertSeverity"))
        $CategoryValue = [string](Get-FirstPropertyValue -InputObject $Alert -PropertyNames @("Category", "AlertCategory"))
        $DirectionValue = [string](Get-FirstPropertyValue -InputObject $Alert -PropertyNames @("MailDirection", "Direction", "MessageDirection"))
        $RecipientTagValue = Get-FirstPropertyValue -InputObject $Alert -PropertyNames @("RecipientTags", "UserTags", "RecipientTag")

        $RecipientTagText = ""
        if ($null -ne $RecipientTagValue) {
            $RecipientTagText = (@($RecipientTagValue) -join " ").ToLowerInvariant()
        }

        $SeverityOk = if (-not [string]::IsNullOrWhiteSpace($SeverityValue)) {
            $SeverityValue.Trim().ToLowerInvariant() -eq "high"
        }
        else {
            $SearchText -match "severity.{0,30}high"
        }

        $CategoryOk = if (-not [string]::IsNullOrWhiteSpace($CategoryValue)) {
            $CategoryValue.Trim().ToLowerInvariant() -eq "threat management"
        }
        else {
            $SearchText.Contains("threat management")
        }

        $DirectionOk = if (-not [string]::IsNullOrWhiteSpace($DirectionValue)) {
            $DirectionValue.Trim().ToLowerInvariant() -eq "inbound"
        }
        else {
            $SearchText.Contains("inbound")
        }

        $RecipientOk = if (-not [string]::IsNullOrWhiteSpace($RecipientTagText)) {
            $RecipientTagText.Contains("priority account")
        }
        else {
            $SearchText.Contains("priority account")
        }

        if ($SeverityOk -and $CategoryOk -and $DirectionOk -and $RecipientOk) {
            return [pscustomobject]@{
                IsMatch       = $true
                AlertIdentity = [string](Get-FirstPropertyValue -InputObject $Alert -PropertyNames @("Identity", "Id", "Name"))
            }
        }
    }

    return [pscustomobject]@{
        IsMatch       = $false
        AlertIdentity = $null
    }
}

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

    $Step1Evidence = [ordered]@{
        Description = "Priority account protection is enabled."
        Assessable  = $false
        Passed      = $null
        CommandUsed = $null
        RawValue    = $null
    }

    $Step2Evidence = [ordered]@{
        Description       = "PRIORITY ACCOUNT tag exists and has assigned members."
        Assessable        = $false
        Passed            = $null
        CommandUsed       = $null
        PriorityTagsFound = 0
        PriorityTagMembers = @()
        AdditionalTags    = @()
    }

    $Step3Evidence = [ordered]@{
        Description = "Required high-severity inbound alert policies for priority accounts are present."
        Assessable  = $false
        Passed      = $null
        CommandUsed = $null
        AlertCount  = 0
        RequiredAlerts = [ordered]@{
            MalwareDetected = [ordered]@{
                Activity      = "Detected malware in an email message"
                Found         = $false
                AlertIdentity = $null
            }
            PhishingDetected = [ordered]@{
                Activity      = "Phishing email detected at time of delivery"
                Found         = $false
                AlertIdentity = $null
            }
        }
    }

    # Step 1: Priority account protection ON
    $TenantSettingsCommand = Get-AvailableCommandName -Names @("Get-EmailTenantSettings")
    if ($TenantSettingsCommand) {
        $Step1Evidence.Assessable = $true
        $Step1Evidence.CommandUsed = $TenantSettingsCommand

        $TenantSettings = & $TenantSettingsCommand
        $ProtectionValue = Get-FirstPropertyValue -InputObject $TenantSettings -PropertyNames @(
            "EnablePriorityAccountProtection",
            "PriorityAccountProtectionEnabled",
            "PriorityAccountProtection",
            "IsPriorityAccountProtectionEnabled"
        )
        $Step1Evidence.RawValue = $ProtectionValue

        if ($null -eq $ProtectionValue) {
            $Step1Evidence.Passed = $null
            $ManualReasons.Add("Unable to determine whether Priority account protection is enabled from tenant settings output.")
        }
        else {
            $Enabled = [bool]$ProtectionValue
            $Step1Evidence.Passed = $Enabled
            if (-not $Enabled) {
                $FailureReasons.Add("Priority account protection is not enabled.")
            }
        }
    }
    else {
        $ManualReasons.Add("Get-EmailTenantSettings cmdlet is unavailable in the current session.")
    }

    # Step 2: PRIORITY ACCOUNT tag configured
    $UserTagCommand = Get-AvailableCommandName -Names @("Get-UserTag", "Get-UserTags")
    if ($UserTagCommand) {
        $Step2Evidence.Assessable = $true
        $Step2Evidence.CommandUsed = $UserTagCommand

        $AllTags = @(& $UserTagCommand)
        $PriorityTags = @($AllTags | Where-Object {
            $TagName = [string](Get-FirstPropertyValue -InputObject $_ -PropertyNames @("Name", "DisplayName", "Identity"))
            $TagName.Trim().ToLowerInvariant() -eq "priority account"
        })

        $Step2Evidence.PriorityTagsFound = @($PriorityTags).Count
        $Step2Evidence.AdditionalTags = @($AllTags |
            Where-Object { $_ -notin $PriorityTags } |
            ForEach-Object { [string](Get-FirstPropertyValue -InputObject $_ -PropertyNames @("Name", "DisplayName", "Identity")) } |
            Where-Object { -not [string]::IsNullOrWhiteSpace($_) })

        if (@($PriorityTags).Count -eq 0) {
            $Step2Evidence.Passed = $false
            $FailureReasons.Add("PRIORITY ACCOUNT user tag was not found.")
        }
        else {
            $MemberEntries = @()
            foreach ($Tag in $PriorityTags) {
                $Members = Get-FirstPropertyValue -InputObject $Tag -PropertyNames @(
                    "Members",
                    "Users",
                    "Recipients",
                    "MemberUsers",
                    "UserList"
                )

                $NormalizedMembers = @($Members | ForEach-Object { [string]$_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
                $MemberEntries += $NormalizedMembers
            }

            $Step2Evidence.PriorityTagMembers = @($MemberEntries | Select-Object -Unique)
            $HasMembers = @($Step2Evidence.PriorityTagMembers).Count -gt 0
            $Step2Evidence.Passed = $HasMembers

            if (-not $HasMembers) {
                $FailureReasons.Add("PRIORITY ACCOUNT tag exists but has no assigned users or groups.")
            }
        }
    }
    else {
        $ManualReasons.Add("No user-tag cmdlet is available (expected Get-UserTag/Get-UserTags).")
    }

    # Step 3: Two required priority-account alert policies configured
    $AlertCommand = Get-AvailableCommandName -Names @("Get-ProtectionAlert", "Get-AlertPolicy", "Get-ActivityAlert")
    if ($AlertCommand) {
        $Step3Evidence.Assessable = $true
        $Step3Evidence.CommandUsed = $AlertCommand

        $Alerts = @(& $AlertCommand)
        $Step3Evidence.AlertCount = @($Alerts).Count

        if (@($Alerts).Count -eq 0) {
            $Step3Evidence.Passed = $false
            $FailureReasons.Add("No alert policies were returned for evaluation.")
        }
        else {
            $MalwareCheck = Test-RequiredAlert -Alerts $Alerts -ActivityText "detected malware in an email message"
            $PhishingCheck = Test-RequiredAlert -Alerts $Alerts -ActivityText "phishing email detected at time of delivery"

            $Step3Evidence.RequiredAlerts.MalwareDetected.Found = [bool]$MalwareCheck.IsMatch
            $Step3Evidence.RequiredAlerts.MalwareDetected.AlertIdentity = $MalwareCheck.AlertIdentity
            $Step3Evidence.RequiredAlerts.PhishingDetected.Found = [bool]$PhishingCheck.IsMatch
            $Step3Evidence.RequiredAlerts.PhishingDetected.AlertIdentity = $PhishingCheck.AlertIdentity

            $Step3Evidence.Passed = ([bool]$MalwareCheck.IsMatch -and [bool]$PhishingCheck.IsMatch)

            if (-not [bool]$MalwareCheck.IsMatch) {
                $FailureReasons.Add("Missing required priority-account alert for activity: Detected malware in an email message (High, Threat management, Inbound, Recipient Tags include Priority account).")
            }
            if (-not [bool]$PhishingCheck.IsMatch) {
                $FailureReasons.Add("Missing required priority-account alert for activity: Phishing email detected at time of delivery (High, Threat management, Inbound, Recipient Tags include Priority account).")
            }
        }
    }
    else {
        $ManualReasons.Add("No alert-policy cmdlet is available (expected Get-ProtectionAlert/Get-AlertPolicy/Get-ActivityAlert).")
    }

    $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 = @(
                "Step 1: E-mail & collaboration > Priority account protection = On",
                "Step 2: PRIORITY ACCOUNT tag exists and members are defined",
                "Step 3: Alert policies include malware/phishing activities with High severity, Threat management category, Inbound direction, and Priority account recipient tag"
            )
            Steps            = [pscustomobject]@{
                Step1 = [pscustomobject]$Step1Evidence
                Step2 = [pscustomobject]$Step2Evidence
                Step3 = [pscustomobject]$Step3Evidence
            }
            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
    }
}