tests/Test-Assessment.41053.ps1

<#
.SYNOPSIS
    Network protection is enabled in block mode.
 
.NOTES
    Test ID: 41053
    Workshop Task: SECOPS-053
    Pillar: SecOps
    Category: Endpoint threat protection
    Required permission: SecurityEvents.Read.All
#>


function Test-Assessment-41053 {
    [ZtTest(
        Category = 'Endpoint threat protection',
        CompatibleLicense = ('ATA'),
        ImplementationCost = 'Low',
        Pillar = 'SecOps',
        RiskLevel = 'High',
        Service = ('Graph'),
        SfiPillar = 'Monitor and detect cyberthreats',
        TenantType = ('Workforce'),
        TestId = 41053,
        Title = 'Network protection is enabled in block mode',
        UserImpact = 'Low'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

    $activity       = 'Checking network protection Secure Score control'
    $controlId      = 'scid_96'
    $controlProfile = $null
    $secureScore    = $null
    $q1Error        = $null
    $q2Error        = $null

    Write-ZtProgress -Activity $activity -Status 'Reading Secure Score control profile (Q1)'
    try {
        # Q1: Read the Secure Score control profile for the pinned network protection control.
        $profileResults = Invoke-ZtGraphRequest -RelativeUri 'security/secureScoreControlProfiles' -Filter "service eq 'MDATP' and id eq '$controlId'" -ApiVersion beta -ErrorAction Stop
        $controlProfile = $profileResults | Select-Object -First 1
    }
    catch {
        $q1Error = $_
        Write-PSFMessage "Failed to retrieve Secure Score control profile: $_" -Tag Test -Level Warning
    }

    Write-ZtProgress -Activity $activity -Status 'Reading latest Secure Score snapshot (Q2)'
    try {
        # Q2: Read the latest Secure Score snapshot.
        $response = Invoke-ZtGraphRequest -RelativeUri 'security/secureScores' -Top '1' -ApiVersion beta -DisablePaging -ErrorAction Stop
        # DisablePaging returns the raw response; unwrap the value array.
        $secureScore = if ($response.value) { $response.value | Select-Object -First 1 } else { $response }
    }
    catch {
        $q2Error = $_
        Write-PSFMessage "Failed to retrieve Secure Score snapshot: $_" -Tag Test -Level Warning
    }
    #endregion Data Collection

    #region Assessment Logic
    # 401/403 on either query → Skipped (SecurityEvents.Read.All not consented or insufficient role).
    foreach ($queryErr in @($q1Error, $q2Error) | Where-Object { $null -ne $_ }) {
        if ((Get-ZtHttpStatusCode -ErrorRecord $queryErr) -in (401, 403)) {
            Add-ZtTestResultDetail -SkippedBecause NotApplicable -Result 'Microsoft Graph returned HTTP 401 or 403; grant `SecurityEvents.Read.All` and assign Security Reader or Security Administrator for delegated runs.'
            return
        }
    }

    # Extract the score entry for scid_96 from the latest snapshot.
    $scoreEntry = $null
    if ($secureScore) {
        $scoreEntry = @($secureScore.controlScores | Where-Object { $_.controlName -eq $controlId }) | Select-Object -First 1
    }

    # Any query error, missing profile, missing snapshot, or missing score entry → Investigate.
    if ($q1Error -or $q2Error -or $null -eq $controlProfile -or $null -eq $secureScore -or $null -eq $scoreEntry) {
        $params = @{
            TestId       = '41053'
            Title        = 'Network protection is enabled in block mode'
            Status       = $false
            Result       = '⚠️ The network protection Secure Score control or latest Secure Score snapshot could not be located, or Microsoft Graph returned an unexpected error.'
            CustomStatus = 'Investigate'
        }
        Add-ZtTestResultDetail @params
        return
    }

    # Determine ignored state from the most recent controlStateUpdates entry.
    $latestStateUpdate = @($controlProfile.controlStateUpdates | Sort-Object { if ($_.updatedDateTime) { [datetime]$_.updatedDateTime } else { [datetime]::MinValue } } -Descending) | Select-Object -First 1
    $isIgnored = $latestStateUpdate -and $latestStateUpdate.state -eq 'ignored'

    $score    = $scoreEntry.score
    $maxScore = $controlProfile.maxScore
    $passed   = ($score -ge $maxScore) -and (-not $isIgnored)

    if ($passed) {
        $testResultMarkdown = "✅ Network protection is enabled in block mode across the eligible Defender for Endpoint estate.`n`n%TestResult%"
    }
    else {
        $testResultMarkdown = "❌ Network protection is disabled, not fully scored, ignored, or set to audit mode.`n`n%TestResult%"
    }
    #endregion Assessment Logic

    #region Report Generation
    $secureScoreUrl  = 'https://security.microsoft.com/securescore'
    $ignoredDisplay  = if ($isIgnored) { '⚠️ Yes' } else { '✅ No' }
    $rowStatus       = if ($passed) { '✅ Pass' } else { '❌ Fail' }
    $lastModified    = if ($controlProfile.lastModifiedDateTime) { Get-FormattedDate -DateString $controlProfile.lastModifiedDateTime } else { '—' }
    $implStatus      = if ($scoreEntry.implementationStatus) { $scoreEntry.implementationStatus } else { '—' }

    $formatTemplate = @'
 
 
### [Microsoft Secure Score]({0})
 
| Control title | Control ID | Score | Max score | Implementation status | Ignored | Last modified | Status |
| :------------ | :--------- | :---- | :-------- | :-------------------- | :------ | :------------ | :----- |
| {1} | {2} | {3} | {4} | {5} | {6} | {7} | {8} |
'@

    $mdInfo = $formatTemplate -f $secureScoreUrl, (Get-SafeMarkdown $controlProfile.title), $controlId, $score, $maxScore, $implStatus, $ignoredDisplay, $lastModified, $rowStatus

    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '41053'
        Title  = 'Network protection is enabled in block mode'
        Status = $passed
        Result = $testResultMarkdown
    }
    Add-ZtTestResultDetail @params
}