Private/Policy.ps1

function ConvertTo-HLPolicyViolation {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Code,

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

        [AllowNull()]
        [object]$Actual,

        [AllowNull()]
        [object]$Threshold
    )

    return [pscustomobject][ordered]@{
        Code      = $Code
        Message   = $Message
        Actual    = $Actual
        Threshold = $Threshold
    }
}

function Get-HLPolicyExceptionState {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$ScanResult,

        [Parameter(Mandatory)]
        [datetime]$AsOf
    )

    $seen = @{}
    $states = New-Object System.Collections.Generic.List[object]
    foreach ($result in @($ScanResult.results)) {
        if ($null -eq $result.exception) {
            continue
        }

        $exception = $result.exception
        $id = if ((Test-HLProperty -InputObject $exception -Name 'id') -and -not [string]::IsNullOrWhiteSpace([string]$exception.id)) {
            [string]$exception.id
        }
        else {
            'CONTROL-' + [string]$result.controlId
        }
        if ($seen.ContainsKey($id)) {
            continue
        }
        $seen[$id] = $true

        $expiry = [datetime]::MinValue
        $expiryValid = (Test-HLProperty -InputObject $exception -Name 'expires') -and
            (Test-HLDateString -Value $exception.expires -ParsedDate ([ref]$expiry))
        $effectiveStatus = if (-not $expiryValid) {
            'InvalidExpiry'
        }
        elseif ($expiry.Date -lt $AsOf.Date) {
            'Expired'
        }
        else {
            'Active'
        }

        $states.Add([pscustomobject][ordered]@{
            Id              = $id
            ControlId       = [string]$result.controlId
            Expires         = if ($expiryValid) { $expiry.ToString('yyyy-MM-dd') } else { [string]$exception.expires }
            EffectiveStatus = $effectiveStatus
        })
    }

    return @($states.ToArray() | Sort-Object Id, ControlId)
}

function Test-HLPolicyEvaluation {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$ScanResult,

        [AllowNull()]
        [Nullable[int]]$MaxFailed,

        [AllowNull()]
        [Nullable[int]]$MaxWarning,

        [AllowNull()]
        [Nullable[double]]$MinimumScore,

        [AllowNull()]
        [Nullable[double]]$MinimumCoverage,

        [switch]$DisallowPartialCollection,

        [switch]$DisallowExpiredExceptions,

        [Parameter(Mandatory)]
        [datetime]$AsOf
    )

    Assert-HLScanResult -ScanResult $ScanResult

    $summary = $ScanResult.summary
    $exceptionStates = @(Get-HLPolicyExceptionState -ScanResult $ScanResult -AsOf $AsOf)
    $expiredExceptions = @($exceptionStates | Where-Object EffectiveStatus -eq 'Expired')
    $invalidExpiryExceptions = @($exceptionStates | Where-Object EffectiveStatus -eq 'InvalidExpiry')
    $metrics = [pscustomobject][ordered]@{
        Total                       = [int]$summary.Total
        Failed                      = [int]$summary.Fail
        Warning                     = [int]$summary.Warning
        HardeningScore              = if ($null -eq $summary.HardeningScore) { $null } else { [double]$summary.HardeningScore }
        EvidenceCoverage            = if ($null -eq $summary.EvidenceCoverage) { $null } else { [double]$summary.EvidenceCoverage }
        PartialCollection           = [bool]$ScanResult.scan.partialCollection
        ExceptionCount              = $exceptionStates.Count
        ExpiredExceptionCount       = $expiredExceptions.Count
        InvalidExceptionExpiryCount = $invalidExpiryExceptions.Count
    }

    $violations = New-Object System.Collections.Generic.List[object]
    if ($null -ne $MaxFailed -and $metrics.Failed -gt [int]$MaxFailed) {
        $violations.Add((ConvertTo-HLPolicyViolation -Code 'MaxFailedExceeded' -Message "Failed controls ($($metrics.Failed)) exceed the allowed maximum ($MaxFailed)." -Actual $metrics.Failed -Threshold ([int]$MaxFailed)))
    }
    if ($null -ne $MaxWarning -and $metrics.Warning -gt [int]$MaxWarning) {
        $violations.Add((ConvertTo-HLPolicyViolation -Code 'MaxWarningExceeded' -Message "Warning controls ($($metrics.Warning)) exceed the allowed maximum ($MaxWarning)." -Actual $metrics.Warning -Threshold ([int]$MaxWarning)))
    }
    if ($null -ne $MinimumScore -and ($null -eq $metrics.HardeningScore -or $metrics.HardeningScore -lt [double]$MinimumScore)) {
        $violations.Add((ConvertTo-HLPolicyViolation -Code 'MinimumScoreNotMet' -Message "Hardening score does not meet the required minimum ($MinimumScore)." -Actual $metrics.HardeningScore -Threshold ([double]$MinimumScore)))
    }
    if ($null -ne $MinimumCoverage -and ($null -eq $metrics.EvidenceCoverage -or $metrics.EvidenceCoverage -lt [double]$MinimumCoverage)) {
        $violations.Add((ConvertTo-HLPolicyViolation -Code 'MinimumCoverageNotMet' -Message "Evidence coverage does not meet the required minimum ($MinimumCoverage)." -Actual $metrics.EvidenceCoverage -Threshold ([double]$MinimumCoverage)))
    }
    if ($DisallowPartialCollection -and $metrics.PartialCollection) {
        $violations.Add((ConvertTo-HLPolicyViolation -Code 'PartialCollectionDisallowed' -Message 'The scan used partial collection, which this policy disallows.' -Actual $true -Threshold $false))
    }
    if ($DisallowExpiredExceptions) {
        foreach ($state in $expiredExceptions) {
            $violations.Add((ConvertTo-HLPolicyViolation -Code 'ExpiredException' -Message "Exception '$($state.Id)' expired on $($state.Expires)." -Actual $state.Expires -Threshold $AsOf.Date.ToString('yyyy-MM-dd')))
        }
        foreach ($state in $invalidExpiryExceptions) {
            $violations.Add((ConvertTo-HLPolicyViolation -Code 'InvalidExceptionExpiry' -Message "Exception '$($state.Id)' has an invalid expiry value." -Actual $state.Expires -Threshold 'yyyy-MM-dd'))
        }
    }

    $passed = $violations.Count -eq 0
    return [pscustomobject][ordered]@{
        Passed       = $passed
        ExitCode     = if ($passed) { 0 } else { 1 }
        ComputerName = [string]$ScanResult.system.ComputerName
        ScanId       = [string]$ScanResult.scan.id
        EvaluatedAsOf = $AsOf.Date.ToString('yyyy-MM-dd')
        Violations   = $violations.ToArray()
        Metrics      = $metrics
    }
}