Private/Compare-IntuneAccessFindingEvidence.ps1

function Compare-IntuneAccessFindingEvidence {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [object] $Before,
        [Parameter(Mandatory)] [object] $After,
        [Parameter(Mandatory)] [DateTimeOffset] $BeforeAt,
        [Parameter(Mandatory)] [DateTimeOffset] $AfterAt
    )
    $prior = Get-IntuneAccessActionCentre -Collection $Before -AsOf $BeforeAt
    $current = Get-IntuneAccessActionCentre -Collection $After -PreviousOutcome @(Get-IntuneAccessProperty $Before 'DeploymentOutcomes' @()) -AsOf $AfterAt
    foreach ($finding in $prior.Findings) {
        $state = 'NotEvaluated'
        $reason = 'No fresh, positive evidence establishes whether this observation cleared.'
        $afterEvidence = $null
        $matchingFindings = @($current.Findings | Where-Object FindingId -EQ $finding.FindingId)
        $currentDevices = @(Get-IntuneAccessProperty $After 'ManagedDevices' @() | Where-Object Id -EQ $finding.DeviceId)
        if ($finding.DeviceId -and $currentDevices.Count -ne 1) {
            $reason = 'The exact device is missing or ambiguous in the later collection. Disappearance is not resolution.'
        }
        elseif ($matchingFindings.Count -gt 0) {
            $state = 'Persisted'; $reason = 'The same finding is present in the later collection. This does not imply a new execution or establish cause.'
            $afterEvidence = $matchingFindings[0].Evidence
        }
        else {
            $devices = @(Get-IntuneAccessProperty $After 'ManagedDevices' @() | Where-Object Id -EQ $finding.DeviceId)
            $inventory = @(Get-IntuneAccessProperty $After 'DeviceInventory' @() | Where-Object Id -EQ $finding.DeviceId)
            $device = if ($devices.Count -eq 1) { $devices[0] } else { $null }
            $lastSync = [DateTimeOffset]::MinValue
            $freshDevice = $null -ne $device -and [DateTimeOffset]::TryParse([string] (Get-IntuneAccessProperty $device 'LastSyncDateTime'), [ref] $lastSync) -and $lastSync -gt $BeforeAt -and $lastSync -le $AfterAt -and ($AfterAt - $lastSync).TotalDays -le 8
            $positive = $false
            if ($finding.SourceType -eq 'DeviceHygiene' -and $freshDevice) {
                $afterEvidence = $device
                switch ($finding.SourceId) {
                    'DEV-CHECKIN-STALE' { $positive = $true }
                    'DEV-CHECKIN-MISSING' { $positive = $true }
                    'DEV-ENROLMENT-NO-HEALTHY-CHECKIN' {
                        $enrolled = [DateTimeOffset]::MinValue
                        $positive = [DateTimeOffset]::TryParse([string] (Get-IntuneAccessProperty $device 'EnrolledDateTime'), [ref] $enrolled) -and $lastSync -gt $enrolled
                    }
                    'DEV-PRIMARY-USER-MISSING' { $positive = -not [string]::IsNullOrWhiteSpace([string] (Get-IntuneAccessProperty $device 'UserId' '')) }
                    'DEV-COMPLIANCE-UNKNOWN' {
                        # Clear the unknown finding without declaring the device compliant.
                        $positive = (Get-IntuneAccessProperty $device 'ComplianceState' '') -in @('compliant', 'noncompliant')
                    }
                    'DEV-ENTRA-CORRELATION' { $positive = $inventory.Count -eq 1 -and (Get-IntuneAccessProperty $inventory[0] 'EntraCorrelationState' '') -eq 'Matched' }
                    'DEV-OS-VERSION-MISMATCH' {
                        if ($inventory.Count -eq 1) {
                            $os = [string] (Get-IntuneAccessProperty $inventory[0] 'OsVersion' '')
                            $positive = $os -ne '' -and $os -eq (Get-IntuneAccessProperty $inventory[0] 'EntraOperatingSystemVersion' '')
                        }
                    }
                }
            }
            elseif ($finding.SourceType -in @('DeploymentOutcome', 'ApplicationEvidence', 'UpdateCompliance') -and $null -ne $device) {
                $coverage = @(Get-IntuneAccessProperty $After 'OutcomeCollectionStatus' @() | Where-Object { $_.WorkloadId -eq $finding.SourceId -and $_.State -eq 'Available' })
                $results = @(Get-IntuneAccessProperty $After 'DeploymentOutcomes' @() | Where-Object { $_.WorkloadId -eq $finding.SourceId -and $_.DeviceId -eq $finding.DeviceId })
                $freshResults = @($results | Where-Object {
                    $time = [DateTimeOffset]::MinValue
                    [DateTimeOffset]::TryParse([string] (Get-IntuneAccessProperty $_ 'LastReportedDateTime'), [ref] $time) -and $time -gt $BeforeAt -and $time -le $AfterAt -and ($AfterAt - $time).TotalDays -le 8
                })
                $positive = $coverage.Count -eq 1 -and $freshResults.Count -gt 0 -and @($freshResults | Where-Object { $_.Category -ne 'Success' -or (Get-IntuneAccessProperty $_ 'DeviceMatchState' '') -ne 'MatchedById' }).Count -eq 0
                $afterEvidence = $freshResults
            }
            elseif ($finding.SourceType -eq 'PolicyConflict') {
                $conflict = $finding.Evidence
                $settings = @(Get-IntuneAccessProperty $After 'PolicySettings' @() | Where-Object SettingDefinitionId -EQ $conflict.SettingDefinitionId)
                $first = @($settings | Where-Object WorkloadId -EQ $conflict.FirstPolicyId)
                $second = @($settings | Where-Object WorkloadId -EQ $conflict.SecondPolicyId)
                $statuses = @(Get-IntuneAccessProperty $After 'PolicyConflictCollectionStatus' @())
                $available = @($statuses | Where-Object { $_.WorkloadId -in @($conflict.FirstPolicyId, $conflict.SecondPolicyId) -and $_.State -eq 'Available' } | Select-Object -ExpandProperty WorkloadId -Unique)
                $collected = [DateTimeOffset]::MinValue
                $freshCollection = [DateTimeOffset]::TryParse([string] (Get-IntuneAccessProperty $After 'CollectedAt'), [ref] $collected) -and $collected -gt $BeforeAt -and $collected -le $AfterAt -and ($AfterAt - $collected).TotalDays -le 8
                $positive = $freshCollection -and $available.Count -eq 2 -and $first.Count -eq 1 -and $second.Count -eq 1 -and [string] $first[0].ValueJson -ceq [string] $second[0].ValueJson
                $afterEvidence = $settings
            }
            elseif ($finding.SourceType -eq 'RemediationEffectiveness' -and $null -ne $device) {
                $results = @($current.RemediationEffectiveness | Where-Object { $_.WorkloadId -eq $finding.SourceId -and $_.DeviceId -eq $finding.DeviceId })
                if ($results.Count -eq 1) {
                    $time = [DateTimeOffset]::MinValue
                    $positive = $results[0].FindingState -eq 'DetectionReportedClear' -and (Get-IntuneAccessProperty $results[0].Evidence 'DeviceMatchState' '') -eq 'MatchedById' -and [DateTimeOffset]::TryParse([string] $results[0].EvidenceTimestamp, [ref] $time) -and $time -gt $BeforeAt
                    $afterEvidence = $results[0]
                }
            }
            if ($positive) {
                $state = 'ObservationCleared'
                $reason = 'Later positive evidence clears this specific observation. This does not prove the external change caused it or that all device problems are resolved.'
            }
            elseif ($finding.DeviceId -and $null -eq $device) {
                $reason = 'The exact device is missing or ambiguous in the later collection. Disappearance is not resolution.'
            }
        }
        [pscustomobject] @{
            FindingId = $finding.FindingId; Title = $finding.Title; DeviceId = $finding.DeviceId; DeviceName = $finding.DeviceName
            VerificationState = $state; Explanation = $reason; BeforeCollectedAt = $BeforeAt; AfterCollectedAt = $AfterAt
            BeforeEvidenceTimestamp = $finding.EvidenceTimestamp; BeforeEvidence = $finding.Evidence; AfterEvidence = $afterEvidence
            CauseState = 'NotAsserted'
        }
    }
}