Private/Get-IntuneAccessRemediationEffectiveness.ps1

function Get-IntuneAccessRemediationEffectiveness {
    [CmdletBinding()]
    param(
        [AllowEmptyCollection()] [object[]] $Outcome = @(),
        [AllowEmptyCollection()] [object[]] $PreviousOutcome = @(),
        [AllowEmptyCollection()] [object[]] $Workload = @(),
        [AllowEmptyCollection()] [object[]] $CollectionStatus = @(),
        [DateTimeOffset] $AsOf = [DateTimeOffset]::UtcNow,
        [ValidateRange(1, 365)] [int] $StaleAfterDays = 8
    )

    # Latest-state APIs do not provide execution history. Earlier snapshots provide
    # observations only; repeated copies of one execution are not repeat failures.
    foreach ($package in @($Workload | Where-Object { (Get-IntuneAccessProperty $_ 'SourceCollection' '') -eq 'Remediations' })) {
        $packageId = [string] $package.Id
        $records = @($Outcome | Where-Object WorkloadId -EQ $packageId)
        $status = @($CollectionStatus | Where-Object WorkloadId -EQ $packageId)
        $collectionAvailable = $status.Count -eq 1 -and $status[0].State -eq 'Available'
        $previousDevices = @($PreviousOutcome | Where-Object { $_.WorkloadId -eq $packageId -and -not [string]::IsNullOrWhiteSpace([string] (Get-IntuneAccessProperty $_ 'DeviceId' '')) } | Group-Object DeviceId)
        foreach ($previousDevice in $previousDevices) {
            if (@($records | Where-Object DeviceId -EQ $previousDevice.Name).Count -eq 0) {
                [pscustomobject] @{
                    Id = "REMEDIATION/$packageId/$($previousDevice.Name)/missing"
                    WorkloadId = $packageId; WorkloadName = [string] $package.Name; DeviceId = $previousDevice.Name
                    DeviceName = [string] (Get-IntuneAccessProperty $previousDevice.Group[0] 'DeviceName' '')
                    FindingState = 'MissingReportedResult'; ObservedState = 'NotReturned'; DetectionState = ''; RemediationState = ''
                    HistoryState = 'PreviousResultNotReturned'; PreviousObservation = @($previousDevice.Group)
                    EvidenceTimestamp = $null; EvidenceAgeDays = $null; Evidence = $null; CollectionAvailable = $collectionAvailable
                    Explanation = 'This device had an earlier result but no current result was returned. This does not prove the package stopped running or that the problem was fixed.'
                    ResolutionState = 'NotConfirmed'; CollectedAt = $AsOf
                }
            }
        }
        if ($records.Count -eq 0) {
            [PSCustomObject] @{
                Id = "REMEDIATION/$packageId"; WorkloadId = $packageId; WorkloadName = [string] $package.Name; DeviceId = ''; DeviceName = ''
                FindingState = if ($collectionAvailable) { 'NoReportedResults' } else { 'NotEvaluated' }
                DetectionState = ''; RemediationState = ''; HistoryState = 'NotEvaluated'; PreviousObservation = $null
                EvidenceTimestamp = $null; EvidenceAgeDays = $null; Evidence = $null; CollectionAvailable = $collectionAvailable
                Explanation = 'No device result is available. This does not establish whether the package ran or which devices should have reported.'
                ResolutionState = 'NotConfirmed'; CollectedAt = $AsOf
            }
            continue
        }
        foreach ($record in $records) {
            $deviceId = [string] (Get-IntuneAccessProperty $record 'DeviceId' '')
            $detection = [string] (Get-IntuneAccessProperty $record 'DetectionState' '')
            $remediation = [string] (Get-IntuneAccessProperty $record 'RemediationState' '')
            $timestamp = Get-IntuneAccessProperty $record 'LastStateUpdateDateTime'
            $parsed = [DateTimeOffset]::MinValue
            $validTime = [DateTimeOffset]::TryParse([string] $timestamp, [ref] $parsed) -and $parsed -le $AsOf
            $age = if ($validTime) { [math]::Round(($AsOf - $parsed).TotalDays, 2) } else { $null }
            $errors = @('PreRemediationDetectionScriptError', 'RemediationScriptError', 'PostRemediationDetectionScriptError' | ForEach-Object {
                [string] (Get-IntuneAccessProperty $record $_ '')
            } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
            $state = 'NotEvaluated'
            $explanation = 'The returned states do not support a definite conclusion.'
            if ($errors.Count -gt 0 -or $detection -eq 'scriptError' -or $remediation -eq 'scriptError') {
                $state = 'ScriptError'; $explanation = 'The returned execution contains a script error. Review the separate detection and remediation errors.'
            }
            elseif ($remediation -eq 'remediationFailed') {
                $state = 'RemediationFailed'; $explanation = 'Intune reported remediation failure; this does not identify its cause.'
            }
            elseif ($remediation -eq 'success') {
                $state = 'RemediationReportedSuccess'; $explanation = 'The remediation reported success. This alone does not prove the underlying problem is resolved.'
            }
            elseif ($detection -eq 'fail') {
                $state = 'DetectedWithoutSuccessfulRemediation'; $explanation = 'Detection reported an issue and no successful remediation was returned.'
            }
            elseif ($detection -eq 'success') {
                $state = 'DetectionReportedClear'; $explanation = 'The detection script reported success for this execution; this is limited to the checks implemented by that script.'
            }
            elseif ($detection -in @('pending', 'notApplicable')) {
                $state = if ($detection -eq 'pending') { 'Pending' } else { 'NotApplicable' }
                $explanation = 'The returned state is not evidence that an issue was fixed.'
            }
            $observedState = $state
            if (-not $validTime -or $age -gt $StaleAfterDays -or -not $collectionAvailable) {
                $state = if ($validTime -and $age -gt $StaleAfterDays) { 'StaleReporting' } else { 'NotEvaluated' }
                $explanation = "Latest recorded assessment: $observedState. Its age or collection coverage prevents a current conclusion."
            }
            $previous = @($PreviousOutcome | Where-Object { $_.WorkloadId -eq $packageId -and (Get-IntuneAccessProperty $_ 'DeviceId' '') -eq $deviceId })
            $history = 'NoBaseline'
            $prior = $null
            if ($deviceId -and $previous.Count -eq 1) {
                $prior = $previous[0]
                $priorTime = [DateTimeOffset]::MinValue
                $priorValid = [DateTimeOffset]::TryParse([string] (Get-IntuneAccessProperty $prior 'LastStateUpdateDateTime'), [ref] $priorTime)
                $history = 'NotEvaluated'
                if ($validTime -and $priorValid -and $parsed -eq $priorTime) { $history = 'SameExecution' }
                elseif ($validTime -and $priorValid -and $parsed -gt $priorTime -and $state -notin @('StaleReporting', 'NotEvaluated')) {
                    $oldDetection = [string] (Get-IntuneAccessProperty $prior 'DetectionState' '')
                    $oldRemediation = [string] (Get-IntuneAccessProperty $prior 'RemediationState' '')
                    $priorErrors = @('PreRemediationDetectionScriptError', 'RemediationScriptError', 'PostRemediationDetectionScriptError' | Where-Object { -not [string]::IsNullOrWhiteSpace([string] (Get-IntuneAccessProperty $prior $_ '')) })
                    $history = 'NewExecutionObserved'
                    if ($state -eq 'ScriptError' -and ($priorErrors.Count -gt 0 -or $oldDetection -eq 'scriptError' -or $oldRemediation -eq 'scriptError')) { $history = 'PersistentScriptError' }
                    elseif ($detection -eq 'fail' -and $oldDetection -eq 'success') { $history = 'IssueReturnedAfterClearDetection' }
                    elseif ($detection -eq 'fail' -and $oldDetection -eq 'fail') { $history = 'RepeatedDetection' }
                    elseif ($detection -ne $oldDetection -or $remediation -ne $oldRemediation) { $history = 'ReportedStatesChanged' }
                }
            }
            elseif ($previous.Count -gt 1) { $history = 'AmbiguousBaseline' }
            [PSCustomObject] @{
                Id = "REMEDIATION/$packageId/$deviceId/$([string] (Get-IntuneAccessProperty $record 'Id' ''))"
                WorkloadId = $packageId; WorkloadName = [string] $package.Name; DeviceId = $deviceId
                DeviceName = [string] (Get-IntuneAccessProperty $record 'DeviceName' '')
                FindingState = $state; ObservedState = $observedState; DetectionState = $detection; RemediationState = $remediation
                HistoryState = $history; PreviousObservation = $prior; EvidenceTimestamp = $timestamp; EvidenceAgeDays = $age
                Evidence = $record; CollectionAvailable = $collectionAvailable; Explanation = $explanation
                ResolutionState = 'NotConfirmed'; CollectedAt = $AsOf
            }
        }
    }
}