Public/Get-IntuneDeliveryChain.ps1

function Get-IntuneDeliveryChain {
    <#
    .SYNOPSIS
    Explains why an Intune policy, app, script or update did or did not apply to a device.
    .DESCRIPTION
    Builds an ordered evidence chain for each workload on a device: check-in, assignment, targeting,
    exclusion, assignment filter, intent and the result Intune reported. The verdict comes from the
    first link that fails or where the evidence stops, and includes a suggested next check.
    Error codes are explained only where Microsoft publishes a meaning. Read only.
    .PARAMETER DeviceName
    Exact Intune managed-device name.
    .PARAMETER WorkloadName
    Optional workload name filter. Wildcards are supported.
    .PARAMETER SnapshotPath
    Use a local IntuneAccess snapshot instead of a live Microsoft Graph collection.
    .PARAMETER ProblemsOnly
    Return only chains that did not end in a reported success.
    .EXAMPLE
    Get-IntuneDeliveryChain -DeviceName 'LAPTOP-0234' -WorkloadName '*VPN*'
    .EXAMPLE
    Get-IntuneDeliveryChain -DeviceName 'LAPTOP-0234' -ProblemsOnly | Format-Table WorkloadName, Verdict, Summary
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $DeviceName,
        [ValidateNotNullOrEmpty()] [string] $WorkloadName = '*',
        [ValidateNotNullOrEmpty()] [string] $SnapshotPath,
        [switch] $ProblemsOnly
    )

    if ($PSBoundParameters.ContainsKey('SnapshotPath')) {
        $collection = Get-IntuneAccessInsightCollection -SnapshotPath $SnapshotPath
        $explanations = @(Get-IntuneAccessProperty $collection 'DeviceAssignmentExplanations' @() | Where-Object DeviceName -EQ $DeviceName)
        $devices = @(Get-IntuneAccessProperty $collection 'ManagedDevices' @() | Where-Object DeviceName -EQ $DeviceName)
        $status = @(Get-IntuneAccessProperty $collection 'OutcomeCollectionStatus' @())
        $asOf = [DateTimeOffset] (Get-IntuneAccessProperty $collection 'GeneratedAt' ([DateTimeOffset]::UtcNow))
        if ($explanations.Count -eq 0) { throw "The snapshot contains no assignment explanations for '$DeviceName'. Explanations are included when the snapshot was taken with device intelligence enabled." }
    }
    else {
        $inventory = Get-IntuneAccessWorkloadAssignments
        $operational = Get-IntuneAccessOperationalEvidence -Workload $inventory.Workloads
        $devices = @($operational.ManagedDevices | Where-Object DeviceName -EQ $DeviceName)
        if ($devices.Count -ne 1) { throw "Expected one matching Intune managed device but found $($devices.Count)." }
        $parsedEntraDeviceId = [guid]::Empty
        $deviceMembership = if ([guid]::TryParse([string] $devices[0].EntraDeviceId, [ref] $parsedEntraDeviceId)) { Get-IntuneAccessDeviceMembership -EntraDeviceId $parsedEntraDeviceId } else { [PSCustomObject] @{ State = 'NotEvaluated'; GroupIds = @(); Groups = @() } }
        $userMembership = Get-IntuneAccessManagedDeviceUserMembership -UserId $devices[0].UserId
        $explanations = @(Resolve-IntuneAccessDeviceAssignment -Device $devices[0] -Workload @($inventory.Workloads) -Assignment @($inventory.Assignments) -DeviceMembership $deviceMembership -UserMembership $userMembership -DeploymentOutcome @($operational.DeploymentOutcomes))
        $status = @($operational.CollectionStatus)
        $asOf = [DateTimeOffset]::UtcNow
    }

    $device = if ($devices.Count) { $devices[0] } else { $null }
    foreach ($explanation in @($explanations | Where-Object { $_.WorkloadName -like $WorkloadName })) {
        $chain = Get-IntuneAccessDeliveryChain -Explanation $explanation -Device $device -OutcomeCollectionStatus $status -AsOf $asOf
        if ($ProblemsOnly -and $chain.Verdict -eq 'Applied') { continue }
        $chain
    }
}