Private/Invoke-ResourceCheck.ps1

function Invoke-ResourceCheck {
    <#
    .SYNOPSIS
        Reusable helper that evaluates a collection of resources against a check scriptblock.
    .DESCRIPTION
        Encapsulates the common pattern of iterating resources, collecting pass/fail results,
        and returning a standardized CISBenchmarkResult. Eliminates boilerplate in custom checks.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$ControlDef,

        [Parameter()]
        [object[]]$Resources = @(),

        [Parameter(Mandatory)]
        [string]$ResourceTypeName,

        [Parameter(Mandatory)]
        [scriptblock]$CheckScript,

        [Parameter()]
        [switch]$FailOnEmpty
    )

    try {
        if ($null -eq $Resources -or $Resources.Count -eq 0) {
            if ($FailOnEmpty) {
                return New-CISCheckResult `
                    -ControlId $ControlDef.ControlId `
                    -Title $ControlDef.Title `
                    -Status 'FAIL' `
                    -Details "No $ResourceTypeName found. This resource is required." `
                    -AffectedResources @("No $ResourceTypeName deployed") `
                    -TotalResources 0 -PassedResources 0 -FailedResources 0
            }
            return New-CISCheckResult `
                -ControlId $ControlDef.ControlId `
                -Title $ControlDef.Title `
                -Status 'PASS' `
                -Details "N/A - No $ResourceTypeName found in the subscription. Control not evaluated." `
                -TotalResources 0 -PassedResources 0 -FailedResources 0
        }

        $totalCount  = $Resources.Count
        $failedList  = [System.Collections.Generic.List[string]]::new()
        $passedCount = 0

        foreach ($resource in $Resources) {
            # CheckScript receives $resource and should return:
            # $null or empty string = PASS
            # non-empty string = FAIL (the string is the failure reason)
            try {
                $failureReason = & $CheckScript $resource
                if ($failureReason) {
                    $failedList.Add([string]$failureReason)
                }
                else {
                    $passedCount++
                }
            }
            catch {
                $resourceName = if ($resource.Name) { $resource.Name } elseif ($resource.ResourceName) { $resource.ResourceName } else { 'Unknown' }
                $failedList.Add("$resourceName [Error: $(Format-CISErrorMessage -Message $_.Exception.Message)]")
            }
        }

        $failedCount = $failedList.Count
        if ($failedCount -gt 0) {
            $details = "$failedCount of $totalCount $ResourceTypeName do not meet requirements: $($failedList -join '; ')"
            return New-CISCheckResult `
                -ControlId $ControlDef.ControlId `
                -Title $ControlDef.Title `
                -Status 'FAIL' `
                -Details $details `
                -AffectedResources $failedList.ToArray() `
                -TotalResources $totalCount `
                -PassedResources $passedCount `
                -FailedResources $failedCount
        }

        return New-CISCheckResult `
            -ControlId $ControlDef.ControlId `
            -Title $ControlDef.Title `
            -Status 'PASS' `
            -Details "All $totalCount $ResourceTypeName meet requirements." `
            -TotalResources $totalCount `
            -PassedResources $passedCount `
            -FailedResources 0
    }
    catch {
        return New-CISCheckResult `
            -ControlId $ControlDef.ControlId `
            -Title $ControlDef.Title `
            -Status 'ERROR' `
            -Details "Error checking ${ResourceTypeName}: $(Format-CISErrorMessage $_.Exception.Message)"
    }
}