Private/Entra/Checks/Invoke-EntraEidscaChecks.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
#
# EIDSCA (Entra ID Security Config Analyzer) check dispatcher. Unlike the per-ID Test-Infiltration*
# checks, EIDSCA is data-driven: it evaluates the catalog in Data/AuditChecks/EidscaChecks.json against
# the raw Graph policy objects our collectors already store. Each control -> one finding, tagged
# compliance.eidsca so it flows through Get-ComplianceCrosswalk -Framework EIDSCA. Honest by design:
# any control whose source object/property wasn't collected returns SKIP ("Not Assessed"), never PASS.

function Invoke-EntraEidscaChecks {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$AuditData
    )

    $catalogPath = Join-Path $PSScriptRoot '../../../Data/AuditChecks/EidscaChecks.json'
    if (-not (Test-Path $catalogPath)) {
        Write-Warning "EIDSCA catalog not found at $catalogPath"
        return @()
    }
    $catalog = Get-Content -Path $catalogPath -Raw | ConvertFrom-Json -AsHashtable

    # The raw Graph objects EIDSCA controls read — all collected by Get-EntraAuthMethodsData /
    # Get-EntraTenantData. Missing pieces simply yield SKIP downstream (Not Assessed).
    $am = $AuditData.AuthMethods
    $sources = @{
        AuthorizationPolicy       = $am.AuthorizationPolicy
        AuthMethodsPolicy         = $am.AuthMethodsPolicy
        MethodConfigurations      = @($am.MethodConfigurations)
        DirectorySettings         = @($am.DirectorySettings)
        AdminConsentRequestPolicy = $AuditData.TenantConfig.AdminConsentRequestPolicy
    }

    $findings = [System.Collections.Generic.List[object]]::new()
    foreach ($check in $catalog.checks) {
        $check['_categoryName'] = $catalog.categoryName
        try {
            $r = Resolve-EidscaControl -Control $check -Sources $sources
            $cv = switch ($r.Status) {
                'SKIP' { 'Not Assessed — required Entra policy/setting was not collected (connect the needed scope/module)' }
                'PASS' { "Compliant (observed: $($r.Actual))" }
                default { "Non-compliant (observed: $($r.Actual); expected: $($check.recommendedValue))" }
            }
            $findings.Add((New-AuditFinding -CheckDefinition $check -Status $r.Status -CurrentValue $cv))
        } catch {
            $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' -CurrentValue "Check failed: $_"))
        }
    }
    return @($findings)
}