modules/AzStack.Insights/rules/AzStackHci.DiagnosticSettings.OsConfig.HealthCheck.ps1

<#
.SYNOPSIS
    Maps a single OS configuration "health section" (as produced by Get-AzStackHciOsConfigSettings)
    onto an Insights rule result.
.DESCRIPTION
    Data-driven rule invoked once per top-level health section returned by the
    AzStackHci.DiagnosticSettings module (e.g. ArcAgentHealth, NonMicrosoftServices, StorageHealth,
    KeyServicesHealth, StabilityEvents, GPOHealthCheck, NetworkATCHealth). Unlike the cross-node
    ConsistencyChecks collection, each of these is a single named object on the health summary carrying
    its own Result / Severity / Message. The stable SECTION NAME (the summary property name) selects a
    fixed identity (GUID + friendly name + remediation) from the locale table, and the upstream Result
    string is mapped onto an InsightStatus. No data is collected or compared here; the module is the
    source of truth.
 
    Result -> InsightStatus (case-insensitive, tolerant of health-status vocabulary):
      PASS / HEALTHY / OK / SUCCESS -> SUCCESS
      WARNING / WARN / DEGRADED -> WARNING
      FAILURE / FAIL / UNHEALTHY / CRITICAL / ERROR -> FAILURE
      INFO / INFORMATIONAL -> INFO
            SKIPPED / NOTAPPLICABLE / NOT_APPLICABLE -> NOT_APPLICABLE
            GPOHealthCheck + SKIPPED -> UNKNOWN (collection was attempted but evidence is unavailable)
      anything else / null -> UNKNOWN
 
    SecurityBaselineHealth and DefenderHealth use the strict OSConfig 1.4 contract:
    PASS, WARNING and SKIPPED, plus baseline aggregate INFO for partial evaluation. Counts, source outcomes and coverage are validated
    without re-evaluating catalog predicates. Baseline PASS requires complete applicable
    coverage; Defender PASS can retain incomplete source coverage. Schema 1.3 reports
    capability unavailable. Invalid security contracts report UNKNOWN independently.
    Status, counts and coverage are projected into the returned Insights object. Selected
    differences and related assignments can be supplied separately to the HTML renderer.
    Raw exports, provenance and error text remain in the linked producer report.
.PARAMETER SchemaVersion
    The OSConfig summary schema, not the producer module or per-node artifact version.
.PARAMETER ExpectedNodes
    Requested nodes from the summary, used to detect omitted security node assessments.
.PARAMETER HtmlEvidence
    Optional report-only destination for confirmed differences and related policy assignments.
    Never attached to the returned rule or its event payload.
#>


[CmdletBinding()]
param(
    # A single health-section object from the Get-AzStackHciOsConfigSettings health summary.
    # Expected members: Result, Severity, Message; optional CheckId, Details, and evidence metadata.
    [Parameter(Mandatory = $true)]
    [psobject]$Section,

    # The stable top-level property name of the section on the summary (e.g. 'ArcAgentHealth').
    # Used to resolve the section's fixed identity/remediation from the locale table.
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$SectionName,

    [version]$SchemaVersion = '1.4',

    [string[]]$ExpectedNodes = @(),

    [System.Collections.IDictionary]$HtmlEvidence
)

# Import localization strings (SectionName -> identity/remediation table) from the locale directory.
Import-LocalizedData -BindingVariable 'localizedData' -BaseDirectory "$PSScriptRoot\locale" -UICulture $PSUICulture

# The live module returns each health section as an [ordered] dictionary (OrderedDictionary), not a
# PSCustomObject. PSObject.Properties does NOT expose dictionary ENTRIES (only the dictionary's own .NET
# members - Count / Keys / Values), so the member-existence guards below would never see Result / Message /
# Details on the raw dictionary and every section would collapse to UNKNOWN. Normalise to a PSCustomObject
# up front so every property read works regardless of the upstream shape. (Round-tripping through
# ConvertTo/From-Json - as the unit tests do - already yields a PSCustomObject, which is why this only
# reproduces against the live module output.)
if ($Section -is [System.Collections.IDictionary]) {
    $Section = [pscustomobject]$Section
}

# Resolve the section's stable identity from its SectionName.
if ($localizedData.Sections.ContainsKey($SectionName)) {
    $definition = $localizedData.Sections[$SectionName]
}
else {
    # Unknown / future section - surface it under a stable generic identity so it is never silently dropped.
    $definition = $localizedData.Fallback
}

$properties = @{
    Name             = $definition.Name
    Description      = $definition.Description
    PublicDocs       = if ($definition.ContainsKey('PublicDocs') -and $definition.PublicDocs) { @($definition.PublicDocs) } else { @() }
    SupportabilityID = 0
}
$insightRule = Initialize-InsightRule -Id $definition.Id -Name $definition.Name -Properties $properties
if ($null -ne $HtmlEvidence) {
    $HtmlEvidence.Remove($definition.Id.ToString())
    $HtmlEvidence.Remove($definition.Id.ToString() + '.SourceCoverage')
}

<# START INSIGHT RULE LOGIC #>

try {
    if ($SectionName -in @('SecurityBaselineHealth', 'DefenderHealth')) {
        if ($null -eq $SchemaVersion -or $SchemaVersion.Major -ne 1) {
            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
        }
        if ($SchemaVersion -lt [version]'1.4') {
            $insightRule.Status = [InsightStatus]::NOT_APPLICABLE
            $insightRule.Properties += [pscustomobject]@{ Message = $localizedData.SecurityCapabilityUnavailable }
            Write-InsightEvent -Insight $insightRule
            return $insightRule
        }
        $isBaseline = $SectionName -eq 'SecurityBaselineHealth'
        $aggregateResults = if ($isBaseline) { @('PASS', 'WARNING', 'INFO', 'SKIPPED') } else { @('PASS', 'WARNING', 'SKIPPED') }
        $requiredFields = @('Severity', 'Result', 'Message', 'Counts', 'EvidenceComplete', 'EvaluatedNodes', 'MissingNodes', 'Details')
        if ($isBaseline) { $requiredFields += 'NotApplicableNodes' } else { $requiredFields += 'CheckId' }
        foreach ($fieldName in $requiredFields) {
            if (-not $Section.PSObject.Properties[$fieldName]) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
        }
        $expectedCheckId = if ($isBaseline) { 'SecurityBaseline' } else { 'DefenderAsr' }
        if (($Section.PSObject.Properties['CheckId'] -and $Section.CheckId -cne $expectedCheckId) -or
            ($isBaseline -and $Section.PSObject.Properties['SchemaVersion'] -and ($Section.SchemaVersion -isnot [string] -or $Section.SchemaVersion -cne '1.0')) -or $Section.Severity -cne 'High' -or
            $Section.Result -isnot [string] -or $Section.Result -cnotin $aggregateResults -or
            $Section.Message -isnot [string] -or $Section.EvidenceComplete -isnot [bool] -or
            $Section.Details -isnot [System.Collections.IList]) {
            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
        }
        $coverageFields = @('EvaluatedNodes', 'MissingNodes')
        if ($isBaseline) { $coverageFields += 'NotApplicableNodes' }
        foreach ($fieldName in $coverageFields) {
            if ($Section.$fieldName -isnot [System.Collections.IList]) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            foreach ($nodeName in $Section.$fieldName) {
                if ($nodeName -isnot [string] -or [string]::IsNullOrWhiteSpace($nodeName)) {
                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                }
            }
        }
        $countNames = if ($isBaseline) { @('Match', 'Delta', 'NotEvaluated', 'NotApplicable') } else { @('Pass', 'Warning', 'Skipped') }
        $requiredCountNames = @('Total') + $countNames
        if ($isBaseline) { $requiredCountNames += @('Evaluated', 'Matched') }
        $counts = $Section.Counts
        if ($counts -is [System.Collections.IDictionary]) { $counts = [pscustomobject]$counts }
        $safeCounts = [ordered]@{}
        foreach ($countName in $requiredCountNames) {
            $hasCount = $null -ne $counts -and $counts.PSObject.Properties[$countName]
            if ((-not $hasCount -and (-not $isBaseline -or $countName -notin @('Matched', 'Evaluated'))) -or
                ($hasCount -and $counts.PSObject.Properties[$countName].Name -cne $countName)) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            $countValue = if ($hasCount) { $counts.$countName } elseif ($countName -eq 'Matched') { $safeCounts.Match } else { $safeCounts.Match + $safeCounts.Delta }
            if ($null -eq $countValue -or [System.Type]::GetTypeCode($countValue.GetType()).ToString() -notin @('Byte', 'SByte', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', 'Single', 'Double', 'Decimal')) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            $numericCount = [double]$countValue
            if ([double]::IsNaN($numericCount) -or [double]::IsInfinity($numericCount) -or $numericCount -lt 0 -or $numericCount -ne [math]::Truncate($numericCount)) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            $safeCounts[$countName] = $countValue
        }
        $sum = 0
        foreach ($countName in $countNames) { $sum += $safeCounts[$countName] }
        $deltaCount = if ($isBaseline) { $safeCounts.Delta } else { $safeCounts.Warning }
        if ($safeCounts.Total -ne $sum -or
            ($isBaseline -and ($safeCounts.Match -ne $safeCounts.Matched -or $safeCounts.Evaluated -ne ($safeCounts.Matched + $safeCounts.Delta))) -or
            ($Section.Result -eq 'WARNING' -and $deltaCount -eq 0) -or
            ($Section.Result -ne 'WARNING' -and $deltaCount -gt 0) -or
            ($Section.Result -eq 'PASS' -and ($safeCounts.Total -eq 0 -or ($isBaseline -and -not $Section.EvidenceComplete)))) {
            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
        }
        $readSecurityRecord = {
            param([object]$Value, [string[]]$Fields = @(), [hashtable]$Types = @{})
            if ($Value -is [System.Collections.IDictionary]) { $Value = [pscustomobject]$Value }
            foreach ($field in @($Fields) + @($Types.Keys)) {
                if ($null -eq $Value -or -not $Value.PSObject.Properties[$field]) {
                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                }
                $fieldValue = $Value.$field
                $validType = switch ($Types[$field]) {
                    'String' { $fieldValue -is [string] }
                    'NullableString' { $null -eq $fieldValue -or $fieldValue -is [string] }
                    'NullableBoolean' { $null -eq $fieldValue -or $fieldValue -is [bool] }
                    'Timestamp' {
                        $timestamp = [datetimeoffset]::MinValue
                        $null -eq $fieldValue -or $fieldValue -is [datetime] -or $fieldValue -is [datetimeoffset] -or
                            ($fieldValue -is [string] -and [datetimeoffset]::TryParse($fieldValue, [ref]$timestamp))
                    }
                    default { $true }
                }
                if (-not $validType) { throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid) }
            }
            return $Value
        }
        $nodeTypes = @{ Node = 'String'; Result = 'String'; Message = 'String'; Source = 'NullableString'; Snapshot = 'NullableString'; Sha256 = 'NullableString'; CapturedAtUtc = 'Timestamp' }
        $settingTypes = @{ CheckId = 'String'; Severity = 'String'; Result = 'String'; Expected = 'NullableString'; BaselineSeverity = 'NullableString' }
        $nodeFields = @('Node', 'Result', 'EvidenceComplete', 'Message', 'Counts', 'Profile', 'Source', 'Snapshot', 'Sha256', 'CapturedAtUtc', 'Settings', 'CollectionErrors')
        $settingFields = @('CheckId', 'Severity', 'BaselineSeverity', 'Expected', 'Result', 'GpoEvidence')
        if ($isBaseline) {
            $nodeTypes.DomainJoined = 'NullableBoolean'; $nodeTypes.OSBuild = 'NullableString'; $nodeTypes.AssessmentStatus = 'String'
            foreach ($fieldName in @('DisplayName', 'Category', 'Expected', 'PublishedExpected', 'RuleSource', 'Validation', 'ExpectedType', 'EvidenceKind', 'Reason', 'Message', 'AssessmentResult')) { $settingTypes[$fieldName] = 'String' }
            foreach ($fieldName in @('ObservedType', 'RegistryPath', 'RegistryValue', 'RelatedCheckId')) { $settingTypes[$fieldName] = 'NullableString' }
            $nodeFields += @('AssessmentStatus', 'OSBuild', 'DomainJoined')
            $settingFields += @('DisplayName', 'AssessmentResult', 'Category', 'PublishedExpected', 'RuleSource', 'Validation', 'ExpectedType', 'Observed', 'ObservedType', 'Present', 'RegistryPath', 'RegistryValue', 'EvidenceKind', 'SourceGpo', 'StoredConfigurationEvidence', 'PolicyObservation', 'Reason', 'Message', 'RelatedCheckId')
        } else {
            $nodeFields += @('CheckId', 'Severity', 'RuntimeStatus')
            $settingFields += @('RuleId', 'OSConfigDefault', 'EvidenceComplete', 'SourceDisagreement', 'Sources')
        }
        $nodeTotals = @{}
        foreach ($countName in $requiredCountNames) { $nodeTotals[$countName] = 0 }
        $seenNodes = @()
        $safeNodeAssessments = @()
        $htmlSourceCoverage = @()
        $storedAssessments = @()
        $relatedAssessments = @()
        $htmlDifferences = @()
        $collectionErrorCount = 0
        $allNodesComplete = $true
        foreach ($detail in $Section.Details) {
            $node = & $readSecurityRecord -Value $detail -Fields $nodeFields -Types $nodeTypes
            if ($node.Node -isnot [string] -or [string]::IsNullOrWhiteSpace($node.Node) -or $node.Node -in $seenNodes -or
                $node.Result -cnotin $aggregateResults -or $node.EvidenceComplete -isnot [bool] -or
                $node.Settings -isnot [System.Collections.IList] -or $node.CollectionErrors -isnot [System.Collections.IList] -or
                ($null -ne $node.Profile -and $node.Profile -cnotin @('DomainJoined', 'Adless'))) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            if (-not $isBaseline -and ($node.CheckId -cne 'DefenderAsr' -or $node.Severity -cne 'High')) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            if ($isBaseline -and (($node.PSObject.Properties['CheckId'] -and $node.CheckId -cne $expectedCheckId) -or
                ($node.PSObject.Properties['SchemaVersion'] -and ($node.SchemaVersion -isnot [string] -or $node.SchemaVersion -cne '1.0')))) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            $seenNodes += $node.Node
            if (-not $node.EvidenceComplete -and (-not $isBaseline -or $node.Node -notin $Section.NotApplicableNodes)) { $allNodesComplete = $false }
            foreach ($collectionError in $node.CollectionErrors) {
                $null = & $readSecurityRecord -Value $collectionError -Types @{ Source = 'String'; Message = 'String' }
            }
            $rowCounts = @{}
            foreach ($countName in $requiredCountNames) { $rowCounts[$countName] = 0 }
            $sourceCoverage = [ordered]@{ NotConfigured = 0; Unavailable = 0; OtherSkipped = 0; NodeUnavailable = ($node.Settings.Count -eq 0) }
            $allSettingsComplete = $true
            $seenSettings = @()
            foreach ($row in $node.Settings) {
                $policyObservation = $null
                if ($isBaseline) {
                    $originalSetting = if ($row -is [System.Collections.IDictionary]) { [pscustomobject]$row } else { $row }
                    $compatibleSetting = [ordered]@{}
                    foreach ($property in $originalSetting.PSObject.Properties) { $compatibleSetting[$property.Name] = $property.Value }
                    $defaults = @{ Reason = $originalSetting.Message; RelatedCheckId = $null; RuleSource = 'PublishedBaseline'; PublishedExpected = $originalSetting.Expected }
                    foreach ($fieldName in $defaults.Keys) {
                        if (-not $compatibleSetting.Contains($fieldName)) { $compatibleSetting[$fieldName] = $defaults[$fieldName] }
                    }
                    $row = $compatibleSetting
                }
                $setting = & $readSecurityRecord -Value $row -Fields $settingFields -Types $settingTypes
                if ($setting.CheckId -isnot [string] -or [string]::IsNullOrWhiteSpace($setting.CheckId) -or $setting.Result -cnotin @('PASS', 'WARNING', 'SKIPPED')) {
                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                }
                $rowCounts.Total++
                if ($isBaseline) {
                    if ($setting.CheckId -in $seenSettings) { throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid) }
                    $seenSettings += $setting.CheckId
                    $assessmentResults = @{ Match = 'PASS'; Delta = 'WARNING'; NotEvaluated = 'SKIPPED'; NotApplicable = 'SKIPPED' }
                    if ($setting.AssessmentResult -cnotin @('Match', 'Delta', 'NotEvaluated', 'NotApplicable') -or
                        $setting.Result -cne $assessmentResults[$setting.AssessmentResult] -or
                        [string]::IsNullOrWhiteSpace($setting.DisplayName) -or
                        $setting.RuleSource -cnotin @('PublishedBaseline', 'NoExpectation') -or
                        ($null -ne $setting.Present -and $setting.Present -isnot [bool]) -or $null -ne $setting.SourceGpo) {
                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                    }
                    if ($setting.Reason -cne $setting.Message -or ($setting.AssessmentResult -in @('NotApplicable', 'NotEvaluated') -and
                        [string]::IsNullOrWhiteSpace($setting.Reason))) {
                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                    }
                    $rowCounts[$setting.AssessmentResult]++
                    $rowCounts.Matched = $rowCounts.Match
                    $rowCounts.Evaluated = $rowCounts.Match + $rowCounts.Delta
                    if ($setting.AssessmentResult -eq 'NotEvaluated') { $allSettingsComplete = $false }
                    if ($setting.RuleSource -ceq 'NoExpectation') {
                        if ($setting.CheckId -cne 'EnabledNTPClient' -or $setting.AssessmentResult -cne 'NotApplicable' -or
                            -not [string]::IsNullOrEmpty($setting.Expected) -or -not [string]::IsNullOrEmpty($setting.PublishedExpected) -or
                            [string]::IsNullOrWhiteSpace($setting.Reason) -or $setting.Message -cne $setting.Reason -or
                            $setting.RelatedCheckId -cne 'TimeSyncHealth') {
                            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                        }
                        $relatedAssessments += [pscustomobject]@{
                            Node = $node.Node
                            CheckId = $setting.CheckId
                            DisplayName = $setting.DisplayName
                            AssessmentResult = $setting.AssessmentResult
                            Reason = $setting.Reason
                            RelatedCheckId = $setting.RelatedCheckId
                        }
                    } elseif ($null -ne $setting.RelatedCheckId) {
                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                    }
                    if ($null -ne $setting.PolicyObservation) {
                        $policyObservation = & $readSecurityRecord -Value $setting.PolicyObservation -Fields @('Collected', 'NativeStatus', 'Sids', 'Value') -Types @{
                            Source = 'String'; PolicyName = 'String'; Type = 'String'
                            Present = 'NullableBoolean'; Error = 'NullableString'
                        }
                        $observationTypes = @{ LsaEnumerateAccountsWithUserRight = 'LsaUserRight'; Win32_UserAccount = 'LocalAccount'; NetUserModalsGet = 'LocalAccountPolicy'; SchannelProtocolRegistry = 'ProtocolMinimum'; SamQueryInformationDomain = 'LocalSecurityPolicy'; LsaQuerySecurityObject = 'LocalSecurityPolicy' }
                        if ($policyObservation.Source -cnotin @($observationTypes.Keys) -or
                            $policyObservation.Type -cne $observationTypes[$policyObservation.Source] -or
                            [string]::IsNullOrWhiteSpace($policyObservation.PolicyName) -or $policyObservation.Collected -isnot [bool] -or
                            $policyObservation.Sids -isnot [System.Collections.IList]) {
                            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                        }
                        $isLsaObservation = $policyObservation.Source -ceq 'LsaEnumerateAccountsWithUserRight'
                        $isProtocolObservation = $policyObservation.Source -ceq 'SchannelProtocolRegistry'
                        $isLocalSecurityObservation = $policyObservation.Type -ceq 'LocalSecurityPolicy'
                        $numericTypes = @('Byte', 'SByte', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', 'Single', 'Double', 'Decimal')
                        if ($null -ne $policyObservation.Value -and $policyObservation.Value -isnot [string]) {
                            if ($isLsaObservation -or $isProtocolObservation -or [System.Type]::GetTypeCode($policyObservation.Value.GetType()).ToString() -notin $numericTypes -or
                                [double]::IsNaN([double]$policyObservation.Value) -or [double]$policyObservation.Value -lt 0 -or
                                [double]$policyObservation.Value -gt [uint32]::MaxValue -or
                                [double]$policyObservation.Value -ne [math]::Truncate([double]$policyObservation.Value)) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                        }
                        $nativeStatus = $policyObservation.NativeStatus
                        if ($null -ne $nativeStatus) {
                            if ([System.Type]::GetTypeCode($nativeStatus.GetType()).ToString() -notin @('Byte', 'SByte', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', 'Single', 'Double', 'Decimal') -or
                                [double]::IsNaN([double]$nativeStatus) -or [double]$nativeStatus -lt 0 -or [double]$nativeStatus -gt [uint32]::MaxValue -or
                                [double]$nativeStatus -ne [math]::Truncate([double]$nativeStatus)) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                        }
                        foreach ($sid in $policyObservation.Sids) {
                            if ($sid -isnot [string] -or [string]::IsNullOrWhiteSpace($sid) -or
                                [System.Security.Principal.SecurityIdentifier]::new($sid).Value -cne $sid) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                        }
                        $samRawValue = $null
                        if ($policyObservation.Source -ceq 'Win32_UserAccount') {
                            if ($null -ne $nativeStatus -or ($policyObservation.Collected -and
                                ($policyObservation.Sids.Count -ne 1 -or $policyObservation.Sids[0] -cnotmatch '^S-1-5-21-\d+-\d+-\d+-(500|501)$'))) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                        } elseif ($policyObservation.Source -ceq 'NetUserModalsGet') {
                            $null = & $readSecurityRecord -Value $policyObservation -Fields @('RawValue')
                            if ($policyObservation.Sids.Count -ne 0 -or ($policyObservation.Collected -and $null -eq $policyObservation.RawValue)) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                            if ($null -ne $policyObservation.RawValue) {
                                $samRawValue = & $readSecurityRecord -Value $policyObservation.RawValue -Fields @('Level', 'Values')
                                if ($null -eq $samRawValue.Level -or
                                    [System.Type]::GetTypeCode($samRawValue.Level.GetType()).ToString() -notin $numericTypes -or
                                    $samRawValue.Level -notin @(0, 3) -or $samRawValue.Values -isnot [System.Collections.IList] -or
                                    $samRawValue.Values.Count -ne (@{ 0 = 5; 3 = 3 }[[int]$samRawValue.Level]) -or $null -eq $nativeStatus -or $nativeStatus -ne 0) {
                                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                }
                                foreach ($rawNumber in $samRawValue.Values) {
                                    if ($null -eq $rawNumber -or [System.Type]::GetTypeCode($rawNumber.GetType()).ToString() -notin $numericTypes -or
                                        [double]::IsNaN([double]$rawNumber) -or [double]$rawNumber -lt 0 -or [double]$rawNumber -gt [uint32]::MaxValue -or
                                        [double]$rawNumber -ne [math]::Truncate([double]$rawNumber)) {
                                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                    }
                                }
                            }
                        }
                        $protocolRows = @()
                        if ($isProtocolObservation) {
                            $null = & $readSecurityRecord -Value $policyObservation -Fields @('Evaluable', 'RawValue') -Types @{ Reason = 'String' }
                            if ($null -ne $nativeStatus -or $policyObservation.Sids.Count -ne 0 -or
                                $policyObservation.Evaluable -isnot [bool] -or $policyObservation.RawValue -isnot [System.Collections.IList] -or
                                ($policyObservation.Collected -and -not [string]::IsNullOrEmpty($policyObservation.Error))) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                            if ($policyObservation.Evaluable) {
                                if (-not $policyObservation.Collected -or $policyObservation.Present -ne $true -or
                                    $null -eq $policyObservation.Value -or $policyObservation.RawValue.Count -eq 0) {
                                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                }
                            } elseif ($null -ne $policyObservation.Present -or $null -ne $policyObservation.Value) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                            foreach ($protocolValue in $policyObservation.RawValue) {
                                $protocolRecord = & $readSecurityRecord -Value $protocolValue -Fields @('Enabled', 'DisabledByDefault') -Types @{
                                    Protocol = 'String'; Version = 'String'; Role = 'String'; RegistryPath = 'String'; State = 'String'
                                }
                                if ($protocolRecord.Role -cnotin @('Client', 'Server') -or
                                    $protocolRecord.State -cnotin @('Enabled', 'Disabled', 'WindowsDefault', 'Invalid', 'Unavailable')) {
                                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                }
                                $privateProtocol = [ordered]@{
                                    Protocol = $protocolRecord.Protocol; Version = $protocolRecord.Version; Role = $protocolRecord.Role
                                    RegistryPath = $protocolRecord.RegistryPath; State = $protocolRecord.State
                                }
                                foreach ($registryName in @('Enabled', 'DisabledByDefault')) {
                                    $registryObservation = & $readSecurityRecord -Value $protocolRecord.$registryName -Fields @('Collected', 'Present', 'Value') -Types @{
                                        Type = 'NullableString'; Error = 'NullableString'
                                    }
                                    if ($registryObservation.Collected -isnot [bool] -or $registryObservation.Present -isnot [bool]) {
                                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                    }
                                    $privateProtocol[$registryName] = [pscustomobject]@{
                                        Collected = $registryObservation.Collected; Present = $registryObservation.Present
                                        Value = $registryObservation.Value; Type = $registryObservation.Type
                                        ErrorPresent = -not [string]::IsNullOrEmpty($registryObservation.Error)
                                    }
                                }
                                $protocolRows += [pscustomobject]$privateProtocol
                            }
                        } elseif ($isLocalSecurityObservation) {
                            $null = & $readSecurityRecord -Value $policyObservation -Fields @('Evaluable', 'RawValue') -Types @{ Reason = 'String'; StatusKind = 'NullableString' }
                            $policyNames = if ($policyObservation.Source -ceq 'SamQueryInformationDomain') { @('PasswordComplexity', 'ClearTextPassword') } else { @('LSAAnonymousNameLookup') }
                            if ($policyObservation.PolicyName -cnotin $policyNames -or $policyObservation.Evaluable -isnot [bool] -or
                                $policyObservation.Sids.Count -ne 0 -or
                                ($null -ne $policyObservation.StatusKind -and $policyObservation.StatusKind -cnotin @('NTSTATUS', 'NET_API_STATUS')) -or
                                ($null -ne $nativeStatus -and $null -eq $policyObservation.StatusKind) -or
                                ($policyObservation.Collected -and ($null -eq $nativeStatus -or $nativeStatus -ne 0 -or
                                    $policyObservation.StatusKind -cne 'NTSTATUS' -or -not [string]::IsNullOrEmpty($policyObservation.Error)))) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                            if ($policyObservation.Evaluable) {
                                if (-not $policyObservation.Collected -or $policyObservation.Present -ne $true -or
                                    $null -eq $policyObservation.Value -or $policyObservation.Value -is [string] -or $policyObservation.Value -notin @(0, 1)) {
                                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                }
                            } elseif ($null -ne $policyObservation.Present -or $null -ne $policyObservation.Value -or
                                [string]::IsNullOrWhiteSpace($policyObservation.Reason) -or $setting.AssessmentResult -cne 'NotEvaluated') {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                            $localSecurityRawValue = $null
                            if ($null -ne $policyObservation.RawValue) {
                                $localSecurityRawValue = & $readSecurityRecord -Value $policyObservation.RawValue -Fields @('Collected', 'NativeStatus', 'PasswordProperties') -Types @{
                                    Stage = 'NullableString'; StatusKind = 'NullableString'; Error = 'NullableString'
                                    LocalDomainName = 'NullableString'; LocalDomainSid = 'NullableString'; DaclSddl = 'NullableString'
                                }
                                if ($localSecurityRawValue.Collected -isnot [bool] -or
                                    $localSecurityRawValue.NativeStatus -cne $nativeStatus -or $localSecurityRawValue.StatusKind -cne $policyObservation.StatusKind -or
                                    ($policyObservation.Collected -and -not $localSecurityRawValue.Collected)) {
                                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                }
                                foreach ($rawNumber in @($localSecurityRawValue.NativeStatus, $localSecurityRawValue.PasswordProperties)) {
                                    if ($null -ne $rawNumber -and ([System.Type]::GetTypeCode($rawNumber.GetType()).ToString() -notin $numericTypes -or
                                        [double]::IsNaN([double]$rawNumber) -or [double]$rawNumber -lt 0 -or [double]$rawNumber -gt [uint32]::MaxValue -or
                                        [double]$rawNumber -ne [math]::Truncate([double]$rawNumber))) {
                                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                    }
                                }
                            } elseif ($policyObservation.Collected -or $null -ne $nativeStatus -or $null -ne $policyObservation.StatusKind) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                        } elseif ($policyObservation.Collected) {
                            if ($policyObservation.Present -ne $true -or $null -eq $policyObservation.Value -or -not [string]::IsNullOrEmpty($policyObservation.Error) -or
                                ($isLsaObservation -and ($null -eq $nativeStatus -or $nativeStatus -notin @(0, 2147483674) -or
                                    (($policyObservation.Sids.Count -eq 0) -ne ($policyObservation.Value.Length -eq 0)) -or
                                    ($nativeStatus -eq 2147483674 -and $policyObservation.Sids.Count -ne 0))) -or
                                ($policyObservation.Source -ceq 'NetUserModalsGet' -and ($null -eq $nativeStatus -or $nativeStatus -ne 0))) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                        } elseif ($null -ne $policyObservation.Present -or $null -ne $policyObservation.Value -or $policyObservation.Sids.Count -ne 0) {
                            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                        }
                    }
                    if ($null -ne $setting.StoredConfigurationEvidence) {
                        $storedAssessment = [ordered]@{
                            Node = $node.Node
                            CheckId = $setting.CheckId
                            BaselineResult = $setting.Result
                            Scope = if ($setting.CheckId -ceq 'DeviceLockAccountLockoutPolicy') { $localizedData.StoredLocalAccountLockoutScope } else { 'Stored configuration only' }
                            Source = $null
                            Collected = $null
                            Present = $null
                            Result = 'UNKNOWN'
                            ContractStatus = 'Invalid'
                        }
                        try {
                            $stored = & $readSecurityRecord -Value $setting.StoredConfigurationEvidence -Fields @('Collected', 'Value') -Types @{
                                Source = 'String'; EvidenceKind = 'String'; PolicyName = 'String'; Artifact = 'NullableString'
                                Present = 'NullableBoolean'; RawValue = 'NullableString'; Result = 'String'; Message = 'String'
                            }
                            if ($stored.Source -cne 'SeceditDefaultDatabaseExport' -or $stored.Collected -isnot [bool] -or
                                $stored.Result -cnotin @('PASS', 'WARNING', 'SKIPPED') -or
                                (-not $stored.Collected -and ($null -ne $stored.Present -or $stored.Result -ne 'SKIPPED')) -or
                                ($stored.Result -ne 'SKIPPED' -and $null -eq $stored.Present)) {
                                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                            }
                            $storedAssessment.Source = $stored.Source
                            $storedAssessment.Collected = $stored.Collected
                            $storedAssessment.Present = $stored.Present
                            $storedAssessment.Result = $stored.Result
                            $storedAssessment.ContractStatus = 'Valid'
                        } catch {
                            $storedAssessment.ContractStatus = 'Invalid'
                        }
                        $storedAssessments += [pscustomobject]$storedAssessment
                    }
                } else {
                    $ruleGuid = [guid]::Empty
                    if ($setting.RuleId -isnot [string] -or -not [guid]::TryParse($setting.RuleId, [ref]$ruleGuid) -or
                        $setting.EvidenceComplete -isnot [bool] -or $setting.SourceDisagreement -isnot [bool] -or
                        $ruleGuid -in $seenSettings -or ($setting.Result -ne 'SKIPPED' -and [string]::IsNullOrWhiteSpace($setting.Expected))) {
                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                    }
                    $seenSettings += $ruleGuid
                    $sources = & $readSecurityRecord -Value $setting.Sources -Fields @('OSConfig', 'DefenderReported', 'PolicyRegistry', 'LocalRegistry')
                    $sourceResults = @()
                    foreach ($sourceName in @('OSConfig', 'DefenderReported', 'PolicyRegistry', 'LocalRegistry')) {
                        $sourceFields = @('Collected', 'Present', 'Value', 'Action', 'Result', 'Message')
                        if ($sourceName -in @('PolicyRegistry', 'LocalRegistry')) { $sourceFields += 'ValueType' }
                        $observation = & $readSecurityRecord -Value $sources.$sourceName -Fields $sourceFields -Types @{ Result = 'String'; Message = 'String'; Action = 'NullableString' }
                        if ($observation.Collected -isnot [bool] -or ($null -ne $observation.Present -and $observation.Present -isnot [bool]) -or
                            $observation.Result -cnotin @('PASS', 'WARNING', 'SKIPPED') -or
                            ($observation.Result -ne 'SKIPPED' -and (-not $observation.Collected -or $observation.Present -ne $true -or $null -eq $observation.Value -or $observation.Value -eq 5))) {
                            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                        }
                        $sourceResults += $observation.Result
                        if ($observation.Result -ceq 'SKIPPED') {
                            $explicitNotConfigured = ($observation.Value -is [string] -and $observation.Value -ceq '5') -or
                                ($null -ne $observation.Value -and [System.Type]::GetTypeCode($observation.Value.GetType()).ToString() -in @('Byte', 'SByte', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', 'Single', 'Double', 'Decimal') -and $observation.Value -eq 5)
                            if (-not $observation.Collected) { $sourceCoverage.Unavailable++ }
                            elseif ($observation.Present -eq $false -or $explicitNotConfigured) { $sourceCoverage.NotConfigured++ }
                            else { $sourceCoverage.OtherSkipped++ }
                        }
                    }
                    $sourceAggregate = if ('WARNING' -in $sourceResults) { 'WARNING' } elseif ('PASS' -in $sourceResults) { 'PASS' } else { 'SKIPPED' }
                    if ($setting.Result -cne $sourceAggregate -or ($setting.EvidenceComplete -and 'SKIPPED' -in $sourceResults)) {
                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                    }
                    $countKey = switch ($setting.Result) { 'PASS' { 'Pass' }; 'WARNING' { 'Warning' }; 'SKIPPED' { 'Skipped' } }
                    $rowCounts[$countKey]++
                    if (-not $setting.EvidenceComplete) { $allSettingsComplete = $false }
                }
                $gpoEvidence = $null
                if (-not $isBaseline -or $null -ne $setting.GpoEvidence) {
                    $gpoEvidence = & $readSecurityRecord -Value $setting.GpoEvidence -Fields @('Collected', 'Message', 'Assignments')
                    if ($gpoEvidence.Collected -isnot [bool] -or $gpoEvidence.Message -isnot [string] -or $gpoEvidence.Assignments -isnot [System.Collections.IList]) {
                        throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                    }
                }
                if ($null -ne $HtmlEvidence) {
                    $assignments = @()
                    $invalidAssignments = 0
                    if ($null -ne $gpoEvidence -and $gpoEvidence.Collected) {
                        foreach ($assignmentValue in $gpoEvidence.Assignments) {
                            try {
                                $assignment = & $readSecurityRecord -Value $assignmentValue -Fields @('Precedence', 'Value', 'ValueType', 'Deleted') -Types @{
                                    GPOName = 'String'; GPOId = 'String'; GPOGuid = 'NullableString'; Result = 'String'
                                }
                                $precedence = 0
                                if ([string]::IsNullOrWhiteSpace($assignment.GPOId) -or
                                    -not [int]::TryParse([string]$assignment.Precedence, [ref]$precedence) -or $precedence -le 0 -or
                                    $assignment.Deleted -isnot [bool] -or $assignment.Result -cnotin @('PASS', 'WARNING', 'SKIPPED') -or
                                    ($assignment.Result -ne 'SKIPPED' -and ($assignment.Deleted -or $null -eq $assignment.Value)) -or
                                    (-not $isBaseline -and $assignment.Result -ne 'SKIPPED')) {
                                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                                }
                                $assignments += [pscustomobject]@{
                                    GPOName = $assignment.GPOName
                                    GPOId = $assignment.GPOId
                                    GPOGuid = $assignment.GPOGuid
                                    Precedence = $precedence
                                    AssignedValue = $assignment.Value
                                    ValueType = $assignment.ValueType
                                    Deleted = $assignment.Deleted
                                    ComparisonResult = $assignment.Result
                                }
                            } catch {
                                $invalidAssignments++
                            }
                        }
                    }
                    $assignmentConflict = $isBaseline -and @($assignments | Where-Object { $_.ComparisonResult -eq 'WARNING' }).Count -gt 0
                    if ($setting.Result -eq 'WARNING' -or $assignmentConflict) {
                        $difference = [ordered]@{
                            Node = $node.Node
                            CheckId = $setting.CheckId
                            DisplayName = if ($isBaseline) { $setting.DisplayName } else { $setting.CheckId }
                            Severity = if ($isBaseline) { $setting.BaselineSeverity } else { $setting.Severity }
                            Expected = $setting.Expected
                            ConfiguredResult = $setting.Result
                            GpoEvidenceCollected = if ($null -ne $gpoEvidence) { $gpoEvidence.Collected } else { $null }
                            InvalidAssignmentCount = $invalidAssignments
                            GpoAssignments = $assignments
                        }
                        if ($isBaseline) {
                            $difference.RegistryPath = $setting.RegistryPath
                            $difference.RegistryValue = $setting.RegistryValue
                            $difference.Validation = $setting.Validation
                            $difference.Observed = $setting.Observed
                            $difference.ObservedType = $setting.ObservedType
                            $difference.Present = $setting.Present
                            $difference.GpoAssignmentConflict = $assignmentConflict
                            $difference.EvidenceKind = $setting.EvidenceKind
                            $difference.PolicyObservation = if ($null -ne $policyObservation) {
                                [pscustomobject]@{
                                    Source = $policyObservation.Source
                                    PolicyName = $policyObservation.PolicyName
                                    Collected = $policyObservation.Collected
                                    Present = $policyObservation.Present
                                    Value = $policyObservation.Value
                                    Type = $policyObservation.Type
                                    NativeStatus = $policyObservation.NativeStatus
                                    Sids = @($policyObservation.Sids)
                                    ErrorPresent = -not [string]::IsNullOrEmpty($policyObservation.Error)
                                }
                            } else { $null }
                            if ($null -ne $policyObservation -and $policyObservation.Source -ceq 'NetUserModalsGet') {
                                $privateRawValue = if ($null -ne $samRawValue) {
                                    [pscustomobject]@{ Level = $samRawValue.Level; Values = @($samRawValue.Values) }
                                } else { $null }
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName RawValue -NotePropertyValue $privateRawValue
                            }
                            if ($null -ne $policyObservation -and $policyObservation.Source -ceq 'SchannelProtocolRegistry') {
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName Evaluable -NotePropertyValue $policyObservation.Evaluable
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName Reason -NotePropertyValue $policyObservation.Reason
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName RawValue -NotePropertyValue $protocolRows
                            }
                            if ($null -ne $policyObservation -and $policyObservation.Type -ceq 'LocalSecurityPolicy') {
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName Evaluable -NotePropertyValue $policyObservation.Evaluable
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName Reason -NotePropertyValue $policyObservation.Reason
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName StatusKind -NotePropertyValue $policyObservation.StatusKind
                                $privateRawValue = if ($null -ne $localSecurityRawValue) {
                                    $nativeFields = [ordered]@{}
                                    foreach ($fieldName in @('Collected', 'Stage', 'NativeStatus', 'StatusKind', 'Error', 'LocalDomainName', 'LocalDomainSid', 'PasswordProperties', 'DaclSddl')) {
                                        $nativeFields[$fieldName] = $localSecurityRawValue.$fieldName
                                    }
                                    [pscustomobject]$nativeFields
                                } else { $null }
                                Add-Member -InputObject $difference.PolicyObservation -NotePropertyName RawValue -NotePropertyValue $privateRawValue
                            }
                        } else {
                            $difference.RuleId = $setting.RuleId
                            $difference.Sources = @(foreach ($sourceName in @('OSConfig', 'DefenderReported', 'PolicyRegistry', 'LocalRegistry')) {
                                $observation = $sources.$sourceName
                                if ($observation.Result -eq 'WARNING') {
                                    [pscustomobject]@{
                                        Source = $sourceName
                                        Value = $observation.Value
                                        Action = $observation.Action
                                        Result = $observation.Result
                                    }
                                }
                            })
                        }
                        $htmlDifferences += [pscustomobject]$difference
                    }
                }
            }
            $nodeCounts = & $readSecurityRecord -Value $node.Counts -Fields (@('Total') + $countNames)
            foreach ($countName in $requiredCountNames) {
                $nodeCountValue = if ($nodeCounts.PSObject.Properties[$countName]) { $nodeCounts.$countName }
                    elseif ($countName -eq 'Matched') { $nodeCounts.Match } else { $nodeCounts.Match + $nodeCounts.Delta }
                if ($null -eq $nodeCountValue -or [System.Type]::GetTypeCode($nodeCountValue.GetType()).ToString() -notin @('Byte', 'SByte', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', 'Single', 'Double', 'Decimal') -or
                    $nodeCountValue -ne $rowCounts[$countName]) {
                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                }
                $nodeTotals[$countName] += $rowCounts[$countName]
            }
            $nodeDelta = if ($isBaseline) { $rowCounts.Delta } else { $rowCounts.Warning }
            $nodeSkipped = if ($isBaseline) { $rowCounts.NotEvaluated } else { $rowCounts.Skipped }
            $nodeEvaluated = if ($isBaseline) { $rowCounts.Match + $rowCounts.Delta -gt 0 } else { $rowCounts.Pass + $rowCounts.Warning -gt 0 }
            $nodeNotApplicable = $isBaseline -and $node.Node -in $Section.NotApplicableNodes
            if ($isBaseline) {
                $nodeAggregate = if ($nodeDelta -gt 0) { 'WARNING' } elseif ($nodeEvaluated -and $node.EvidenceComplete) { 'PASS' }
                    elseif ($nodeEvaluated) { 'INFO' } else { 'SKIPPED' }
                $assessmentStatus = if ($nodeNotApplicable) { 'NotApplicable' } elseif ($nodeDelta -gt 0) { 'Delta' }
                    elseif ($nodeEvaluated -and $node.EvidenceComplete) { 'Match' } elseif ($nodeEvaluated) { 'Partial' } else { 'NotEvaluated' }
                if ($node.Result -cne $nodeAggregate -or $node.AssessmentStatus -cne $assessmentStatus) {
                    throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
                }
            }
            if (($nodeEvaluated -and ($null -eq $node.Profile -or ($isBaseline -and $null -eq $node.DomainJoined))) -or
                ($nodeEvaluated -ne ($node.Node -in $Section.EvaluatedNodes)) -or
                ((-not $node.EvidenceComplete -and -not $nodeNotApplicable) -ne ($node.Node -in $Section.MissingNodes)) -or
                ($nodeNotApplicable -and ($node.Result -ne 'SKIPPED' -or $node.AssessmentStatus -ne 'NotApplicable' -or
                    ($rowCounts.Total -gt 0 -and ($rowCounts.NotApplicable -ne $rowCounts.Total -or $rowCounts.Evaluated -ne 0 -or $rowCounts.NotEvaluated -ne 0))))) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            if (($node.EvidenceComplete -and (-not $allSettingsComplete -or $rowCounts.Total -eq 0)) -or
                ($node.Result -eq 'WARNING' -and $nodeDelta -eq 0) -or ($node.Result -ne 'WARNING' -and $nodeDelta -gt 0) -or
                ($node.Result -eq 'PASS' -and ($rowCounts.Total -eq 0 -or $nodeSkipped -gt 0 -or ($isBaseline -and -not $node.EvidenceComplete)))) {
                throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
            }
            $collectionErrorCount += $node.CollectionErrors.Count
            $safeNodeAssessment = [ordered]@{
                Node = $node.Node
                Result = $node.Result
                EvidenceComplete = $node.EvidenceComplete
                Counts = [pscustomobject]$rowCounts
                CollectionErrorCount = $node.CollectionErrors.Count
            }
            if ($isBaseline) {
                $safeNodeAssessment.CheckId = $expectedCheckId
                if ($node.PSObject.Properties['SchemaVersion']) { $safeNodeAssessment.SchemaVersion = $node.SchemaVersion }
            }
            elseif ($null -ne $HtmlEvidence) {
                $htmlSourceCoverage += [pscustomobject]@{ Node = $node.Node; SourceCoverage = [pscustomobject]$sourceCoverage }
            }
            $safeNodeAssessments += [pscustomobject]$safeNodeAssessment
        }
        foreach ($countName in $requiredCountNames) {
            if ($nodeTotals[$countName] -ne $safeCounts[$countName]) { throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid) }
        }
        if ($ExpectedNodes.Count -gt 0 -and (@($ExpectedNodes | Where-Object { $_ -notin $seenNodes }).Count -gt 0 -or @($seenNodes | Where-Object { $_ -notin $ExpectedNodes }).Count -gt 0)) {
            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
        }
        foreach ($fieldName in $coverageFields) {
            foreach ($nodeName in $Section.$fieldName) {
                if ($nodeName -notin $seenNodes) { throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid) }
            }
        }
        $applicableSkippedNodes = @($Section.Details | Where-Object { $_.Result -eq 'SKIPPED' -and (-not $isBaseline -or $_.Node -notin $Section.NotApplicableNodes) })
        $aggregateResult = if ($deltaCount -gt 0) { 'WARNING' } elseif ($safeCounts.Total -gt 0 -and
            (($isBaseline -and $safeCounts.Evaluated -gt 0 -and $Section.EvidenceComplete) -or (-not $isBaseline -and $applicableSkippedNodes.Count -eq 0 -and $safeCounts.Skipped -eq 0))) { 'PASS' }
            elseif ($isBaseline -and $safeCounts.Evaluated -gt 0) { 'INFO' } else { 'SKIPPED' }
        if (($Section.EvidenceComplete -and (-not $allNodesComplete -or $Section.EvaluatedNodes.Count -eq 0 -or $Section.MissingNodes.Count -gt 0)) -or
            ($Section.Result -eq 'PASS' -and $applicableSkippedNodes.Count -gt 0) -or $Section.Result -cne $aggregateResult) {
            throw [System.IO.InvalidDataException]::new($localizedData.SecurityContractInvalid)
        }
        if ($null -ne $HtmlEvidence -and $htmlDifferences.Count -gt 0) {
            $HtmlEvidence[$definition.Id.ToString()] = $htmlDifferences
        }
        if ($null -ne $HtmlEvidence -and -not $isBaseline) {
            $HtmlEvidence[$definition.Id.ToString() + '.SourceCoverage'] = $htmlSourceCoverage
        }
        $safeSection = [ordered]@{
            Result = $Section.Result
            Message = $localizedData.SecurityResultMessage -f $definition.Name, $Section.Result
            EvidenceComplete = $Section.EvidenceComplete
            Counts = [pscustomobject]$safeCounts
            CollectionErrorCount = $collectionErrorCount
            NodeAssessments = $safeNodeAssessments
        }
        if ($isBaseline) {
            $safeSection.CheckId = $expectedCheckId
            if ($Section.PSObject.Properties['SchemaVersion']) { $safeSection.SchemaVersion = $Section.SchemaVersion }
            $safeSection.StoredConfigurationAssessments = $storedAssessments
            $safeSection.RelatedAssessments = $relatedAssessments
        }
        foreach ($fieldName in $coverageFields) { $safeSection[$fieldName] = $Section.$fieldName }
        $Section = [pscustomobject]$safeSection
    }
    $result = if ($Section.PSObject.Properties['Result'] -and $Section.Result) { [string]$Section.Result } else { '' }

    switch -Regex ($result) {
        '^(PASS|HEALTHY|OK|SUCCESS)$'                      { $insightRule.Status = [InsightStatus]::SUCCESS }
        '^(WARNING|WARN|DEGRADED)$'                        { $insightRule.Status = [InsightStatus]::WARNING }
        '^(FAILURE|FAIL|UNHEALTHY|CRITICAL|ERROR)$'        { $insightRule.Status = [InsightStatus]::FAILURE }
        '^(INFO|INFORMATIONAL)$'                           { $insightRule.Status = [InsightStatus]::INFO }
        '^(SKIPPED|NOTAPPLICABLE|NOT_APPLICABLE)$'         { $insightRule.Status = [InsightStatus]::NOT_APPLICABLE }
        default                                            { $insightRule.Status = [InsightStatus]::UNKNOWN }
    }

    if ($SectionName -eq 'GPOHealthCheck' -and $result -eq 'SKIPPED') {
        $insightRule.Status = [InsightStatus]::UNKNOWN
    }

    # Capture the upstream section message (a short human-readable summary of the section outcome, e.g.
    # "No domain GPOs applied - only Local Group Policy detected").
    $sectionMessage = if ($Section.PSObject.Properties['Message'] -and $Section.Message) {
        [string]$Section.Message
    }
    else {
        ''
    }

    # Schema 1.3 identifies which nodes contributed evidence. Preserve that contract in the rule output so a
    # retained FAILURE/WARNING cannot be mistaken for a complete-cluster observation, and a SKIPPED result
    # shows exactly where evidence was unavailable.
    $evidence = [ordered]@{}
    foreach ($fieldName in @('EvidenceComplete', 'EvaluatedNodes', 'MissingNodes', 'NotApplicableNodes', 'Counts', 'CollectionErrorCount', 'NodeAssessments', 'StoredConfigurationAssessments', 'RelatedAssessments')) {
        if ($Section.PSObject.Properties[$fieldName]) {
            $evidence[$fieldName] = $Section.$fieldName
        }
    }
    if ($evidence.Count -gt 0) {
        if ($SectionName -eq 'SecurityBaselineHealth') {
            foreach ($fieldName in @('CheckId', 'SchemaVersion')) {
                if ($Section.PSObject.Properties[$fieldName]) { $evidence[$fieldName] = $Section.$fieldName }
            }
        }
        $insightRule.Properties += [pscustomobject]$evidence
    }

    # On a healthy / informational outcome, surface the confirming message as a property row so the section
    # reads meaningfully in the report instead of blank (e.g. a clean GPO check shows "No domain GPOs applied -
    # only Local Group Policy detected"). On FAILURE/WARNING the message is carried as ErrorMessage (below), so
    # it is not duplicated here.
    if ($sectionMessage -and $insightRule.Status -ne [InsightStatus]::FAILURE -and $insightRule.Status -ne [InsightStatus]::WARNING) {
        $insightRule.Properties += [pscustomobject]@{ Message = $sectionMessage }
    }

    # Surface the upstream per-item detail (if any) so the HTML report shows the contributing items
    # per node on every status (e.g. the non-Microsoft services present on each node) - this per-node
    # inventory is useful information even when the section result is healthy.
    if ($Section.PSObject.Properties['Details'] -and $Section.Details) {
        foreach ($detail in @($Section.Details)) {
            $insightRule.Properties += $detail
        }
    }

    # Mixed GPO evidence reports nodes with applied domain GPOs in Details and nodes whose evidence is missing
    # in a separate UncollectedNodes string array. Project those missing nodes as report rows as well.
    if ($SectionName -eq 'GPOHealthCheck' -and $Section.PSObject.Properties['UncollectedNodes'] -and $Section.UncollectedNodes) {
        $existingNodes = @($insightRule.Properties | ForEach-Object {
                $propertyRow = $_
                if ($propertyRow -is [System.Collections.IDictionary]) {
                    $propertyRow = [pscustomobject]$propertyRow
                }
                if ($propertyRow.PSObject.Properties['Node']) {
                    [string]$propertyRow.Node
                }
            })

        foreach ($uncollectedNode in @($Section.UncollectedNodes)) {
            if ([string]$uncollectedNode -notin $existingNodes) {
                $insightRule.Properties += [pscustomobject]@{
                    Node   = [string]$uncollectedNode
                    Status = 'Not collected'
                }
            }
        }
    }

    # On a non-healthy outcome, carry the upstream message + the section-specific remediation.
    if ($insightRule.Status -eq [InsightStatus]::FAILURE -or $insightRule.Status -eq [InsightStatus]::WARNING) {
        $insightRule.ErrorMessage = if ($sectionMessage) { $sectionMessage } else { ($localizedData.DefaultErrorMessage -f $definition.Name) }
        $insightRule.ErrorCode    = $definition.ErrorCode
        $insightRule.Remediation  = $definition.Remediation
    }

    Write-AzsSupportLog -Message ($localizedData.Evaluated -f $SectionName, $result, $insightRule.Status)
}
catch {
    $_ | Write-AzsSupportLog -Level 'Exception'
    $insightRule.ScriptStackTrace = Get-FormattedException -Exception $_
    $insightRule.Status = [InsightStatus]::UNKNOWN
}

<# END INSIGHT RULE LOGIC #>

$insightRule.Duration = New-TimeSpan -Start $insightRule.OccurrenceTimeUTC -End $([System.DateTime]::UtcNow)

Write-InsightEvent -Insight $insightRule
return $insightRule

# SIG # Begin signature block
# MIInKAYJKoZIhvcNAQcCoIInGTCCJxUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAejYLU0bv8VxKu
# Cp493ui2uNBhpKKoEeOZBPVnX+y/KqCCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1
# OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8
# o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg
# 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4
# Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R
# X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk
# ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw
# HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg
# UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh
# MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9
# s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H
# VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3
# w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n
# 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs
# A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo
# Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb
# SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6
# 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z
# V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v
# 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs
# /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA
# AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow
# VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh
# emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h
# KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd
# M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp
# yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t
# Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5
# REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs
# 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK
# Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5
# pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW
# eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ
# 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI
# MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4
# NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh
# ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q
# hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU
# nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb
# H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z
# uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u
# vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW
# 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV
# DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10
# 1cY2L4A7GTQG1h32HHAvfQESWP0xghnEMIIZwAIBATBuMFcxCzAJBgNVBAYTAlVT
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w
# DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ
# KoZIhvcNAQkEMSIEIKL3jznZcvDeQBlT4FnmMM/fK4eEbQWTAZVUNqOkUMCvMEIG
# CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAGeXh4U0g8GAFBCrX
# IDhiovMhX0teGEfYJwbbcAvuAydzJM+6+h4rxuACgq0ZKxgalu9i4rsFgOBaTWKJ
# OtRcOs20U0qItP4+OzRgbSAP2EfxgB0D8btmEZC7Eti6WhifxpXUNAgA19RohfF3
# D4o9njqmnNljMgTys1M7NNb/3n1owEmMpVLzJ6eZnFQA8YpQOpsERj4BEyx+7NrH
# aQ+aboODhyL8E38XD5FkdxYFHr9giGbNVxZ2/omVCBLHWA6HXE5/kvSnXNL0CbX8
# F7nL+4/UdMRFfIXeVq9xVx8HtJmJaBVadGa3I7oilVyJ2fDvBEFufhLlNqHqXACv
# pAtPP6GCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20w
# ghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9
# MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCBkK8epeOcxB7F+
# Cxt3fHEpWoeHzh7wTyiKM0ew8jYffQIGaqqQ5MoJGBMyMDI2MTEwMjE0NDczNy4y
# MDlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw
# JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RjAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMT
# HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgIT
# MwAAAiAk4ebgF7m0jgABAAACIDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTM5NTJaFw0yNzA1MTcxOTM5NTJa
# MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk
# IFRTUyBFU046RjAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDR
# YY7yr7ijW6CR178uKveIMufutWOicxgJwKOce/2GOQceus6ZWfX14i3jNg3JOP7M
# GJMkOAucwWBwiA8URp+ZYkGjpVoVkGZsV27WjqLwpf2AwqBsJ/TzqwE7JFFaxup3
# Ldxj8GjdJymDFRrdVN/pYHoBFrjD1IkIDu8b1CWn8tgomiKRSY+STvJq99mVkdph
# MBIUGOegQny8qRd24VME0xi8Oomks9Zq9EjDeKHGpvAbXUEQ6m3cROoEPhTE/miw
# eQH9TqJt3IOsqPv3L8urojB747XBC2y0CDIHlKLcLl3ZG8D7JXKnWTFen3msMPJp
# cvrQ3zUBVJrH/mI3RxHmCh9ppDP0uG1+PJwk6H/x+sfoG9hW64xoXkpx6DEfNZNf
# cXdKbXF28XEXdLNnzo3SLNVymeQJhNqOSKhnU84QnKmrjEk541JiurlDCkCWO9lU
# BUMb9x0nyfXUbNRPVLgP+PTMRdXOowJdYCzCQfN2ZqL0s4YI28F1Dbn7Bgw2E4P1
# E9unsvMzJHtzhS2Th3TpCfBbOGalIlF9x/DJZ/ssm/yyzT9YtIFeqmfNxBPTE3aO
# uh6HxmTICzfYAATvWNhBbo19QwsjPeA9JvhqTLC2KUNgrXroGy4eDZo0n7jFYjZk
# Uih1Ty+8E6qEvV2Na6Z5gUyD5a+tHGDmq69CmUiHfwIDAQABo4IBSTCCAUUwHQYD
# VR0OBBYEFNvInOCIhxGA8mY7l1g07UHvyNgzMB8GA1UdIwQYMBaAFJ+nFV0AXmJd
# g/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9z
# b2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGlt
# ZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0l
# AQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUA
# A4ICAQCtKGBto1BSvm4WFI+J0NSyVhU1LHL7F3fbjZ2d7F5Kn/FCTBZXpzrDVl63
# FLRNcIFpnJy4/nlg43r7T5sJPdo4Ms8ADSHQEJnHSu3x9UpjCzREBPi9+nHhvDgR
# x/1WmBD6gQUZJLOhcN2TxW4KJyhinMtiBFtkNRZ2vmZ1MAdNXTm5d0Lwk3wzj+/f
# 7VCCTWCXJSoqNa3VU/6sACHI97Evbnzg8bd3hxrfz6CcCVuf77egvRHinthJuwSR
# ePP7aVmcevb1nWUIAICdBebHQOrzNIeWBIQwvcFaS3SFc+49rqrwQOMFDR4FYBzS
# 7b0QeBVxFuLL2iVu4KAHMNUhLLSD4iKLDFBNTOtTzTlhGvMgG77A1cjeQrDMHa6o
# ReMDeUDqHUrxv8g7IRdIh+h0gDLkzN0xIuzli0Bv7JtybGJbV6JxaDF4CzSCIMRp
# K59nI6iKo4LgnbQBZJW7+6akYsKG/pXPlfxNv2InpD10tSCkCvw9kr6W1+NRN+Eu
# ZczRgAwWlcK9XJZ3uu/v/oxHtO7/kmVIs51F9qV6Y2QNXd6tU46YPrK98m2QDys+
# lvLNimK0e1xZ7Z1GawKohKGvlLALWDlZQqgHfJ31CB0LlIDI7iLyYTpd2iyKjqsk
# bQiyMtICH+RmH/oCg7JOK0ZA3XIMba9aSWgBF3QZ6pG3EGeQqjCCB3EwggVZoAMC
# AQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m
# dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIy
# NVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9
# DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2
# Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N
# 7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXc
# ag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJ
# j361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjk
# lqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37Zy
# L9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M
# 269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLX
# pyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLU
# HMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode
# 2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEA
# ATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYE
# FJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEB
# MEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# RG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEE
# AYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
# /zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEug
# SaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9N
# aWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsG
# AQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jv
# b0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt
# 4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsP
# MeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++
# Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9
# QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2
# wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aR
# AfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5z
# bcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nx
# t67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3
# Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+AN
# uOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/Z
# cGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCB
# yzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMc
# TWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBU
# U1MgRVNOOkYwMDItMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T
# dGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCTGA9vpsJ6glqCLmI0rggGx4YE
# EqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3
# DQEBCwUAAgUA7pL9HDAiGA8yMDI2MTEwMjEyMTQyMFoYDzIwMjYxMTAzMTIxNDIw
# WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDukv0cAgEAMAcCAQACAgSrMAcCAQAC
# AhSRMAoCBQDulE6cAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKg
# CjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBADj9E34d
# MSCOfTcHBfUs+2tv/3gGySEGKyZNjlxFHTkw4LuDq+WzcE0AFo2XE5mNUaD+oY/g
# ++eKIIoEd6d7kNJKWGeydEnQQHX1t08/1p8wMnFmOJfiJ5IVoiAASH3sNWP5Z+SG
# GUvGWdXrfFDySG1aLmotfH5gY+hfwTrK43+2ZVgd+VB1172mR5BTyVe5LjKDLyQf
# Z8rXl5QLpwDx61fLTn3A/ev6H7fLNiNqnISJwpa7FPDMaXBJj1krj76t6KfCmwad
# UsVGlWdUJhQv61RXVRYUC/31A2emR482lnyR/fx8u3RY2e9Il6rx/mkILfzDueeq
# HKBX25vDjFPDQvYxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMAITMwAAAiAk4ebgF7m0jgABAAACIDANBglghkgBZQMEAgEFAKCCAUow
# GgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDdIWRE
# KKlu9FNVDFoNvPIaOEKtYeS550sJYYezcK7KBjCB+gYLKoZIhvcNAQkQAi8xgeow
# gecwgeQwgb0EION7vyOlPA1VqlEp0QIVGlNd8S5YWBnKj97LuTWHSO2vMIGYMIGA
# pH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIgJOHm4Be5tI4A
# AQAAAiAwIgQgZCelp6Ygv320LHujoCeFRX8oZzewVwqSLYwrizmV+fswDQYJKoZI
# hvcNAQELBQAEggIAHO0lMngZqTxzGycJ+md4ejRtMdnrM6FVJYrX6KADgXRMuu8l
# 5QDheehGpQDMnwMreum9muqS0c+J46VxRlV91Dsgr1J9LIrC3nTXEYaJzDNBsMq3
# i0Q3wyGRKSW0V6KZDobKki+LDn+bJXnqHmQxh4Eq6E3dd8cf27sAqR6VGuu2XTTv
# 538S3s5L1e/2Rvv1+nRAqAIhFFMSeGQ7pG1OqVFPU+JObt727jFLTOnroqUWPdtu
# +eeTJjRoKfK5Go72SfYMjveTTHl4SbAgE1GDGY3nw72F5u2STRQBZDxus9pL6b2o
# P3hBjvmtYTio6MTEukNbgKfM3aC4p2EEIE+0r4FKoLR93ijxzHgWiqOz53pkejE7
# lGuFtTl3PCihp/N1TFkuW2uK3VeMD9DxOgP65T404QrxrmhISABNrCQeiywmYpvh
# NJIvTo5IyipBGfJQP3fOMYStr/6CWnGI6rPhFlBOxcDWPbjDX3ZhwWy09+P4sfes
# f4qZivsU1Rhd4EhQhu8vyfmZWLLS2xsBwCcnnx2Ge+2o/lC3kNYLULW800tx66ia
# Oaew8yX9tIw+s9OZnEEMO88AnLOQk1rhHSkYsSaHBf77D6fXkcmv0B3IhJcNNPHP
# bWSQVCZ1XHuxBkITWqUh14lw4MbO5zrwa4phQkO7RwAG9udSA694AIalyTo=
# SIG # End signature block