Public/Get-CMSoftwareUpdateCompliance.ps1

function Get-CMSoftwareUpdateCompliance {
    <#
    .SYNOPSIS
        Software update compliance per device for a Software Update Group (site) or live from clients.
    .DESCRIPTION
        Site mode: uses SMS_AuthorizationList (SUG) -> Updates CI_IDs, then SMS_UpdateComplianceStatus for
        members of -CollectionId. Status: 0 Unknown, 1 NotRequired, 2 Required (missing), 3 Installed.
        Live mode: lists CCM_SoftwareUpdate (applicable, not yet installed) on each client.
    .PARAMETER SiteCode
        Site code.
    .PARAMETER ProviderMachineName
        SMS Provider server.
    .PARAMETER SoftwareUpdateGroupName
        SUG name (LocalizedDisplayName).
    .PARAMETER CollectionId
        Collection to evaluate.
    .PARAMETER Summary
        Return one row per device with counts instead of per-update rows.
    .PARAMETER ComputerName
        Live mode targets.
    .EXAMPLE
        Get-CMSoftwareUpdateCompliance -SiteCode HOU -ProviderMachineName cm01 -SoftwareUpdateGroupName 'HOU - 2026-10 Pilot' -CollectionId HOU00042 -Summary
    .EXAMPLE
        Get-CMSoftwareUpdateCompliance -ComputerName PC01
    #>

    [CmdletBinding(DefaultParameterSetName = 'Site')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Site')][string]$SiteCode,
        [Parameter(Mandatory, ParameterSetName = 'Site')][Alias('SiteServer')][string]$ProviderMachineName,
        [Parameter(Mandatory, ParameterSetName = 'Site')][string]$SoftwareUpdateGroupName,
        [Parameter(Mandatory, ParameterSetName = 'Site')][string]$CollectionId,
        [Parameter(ParameterSetName = 'Site')][switch]$Summary,
        [Parameter(Mandatory, ParameterSetName = 'Live')][string[]]$ComputerName
    )
    $evalMap = @{ 0 = 'None'; 1 = 'Available'; 2 = 'Submitted'; 3 = 'Detecting'; 4 = 'PreDownload'; 5 = 'Downloading'; 6 = 'WaitInstall'; 7 = 'Installing'; 8 = 'PendingSoftReboot'; 9 = 'PendingHardReboot'; 10 = 'WaitReboot'; 11 = 'Verifying'; 12 = 'InstallComplete'; 13 = 'Error'; 14 = 'WaitServiceWindow' }

    if ($PSCmdlet.ParameterSetName -eq 'Live') {
        foreach ($c in $ComputerName) {
            try {
                Get-CimInstance -ComputerName $c -Namespace root\ccm\ClientSDK -ClassName CCM_SoftwareUpdate -ErrorAction Stop |
                    ForEach-Object {
                        [pscustomobject]@{ ComputerName = $c; ArticleID = "KB$($_.ArticleID)"; Name = $_.Name; State = $evalMap[[int]$_.EvaluationState]; Deadline = $_.Deadline; ErrorCode = $_.ErrorCode }
                    }
            } catch { Write-Warning "$c : $($_.Exception.Message)" }
        }
        return
    }

    $cim = @{ ComputerName = $ProviderMachineName; Namespace = "root\SMS\site_$SiteCode"; ErrorAction = 'Stop' }
    $sug = Get-CimInstance @cim -ClassName SMS_AuthorizationList -Filter "LocalizedDisplayName='$($SoftwareUpdateGroupName -replace "'", "''")'"
    if (-not $sug) { throw "SUG '$SoftwareUpdateGroupName' not found." }
    $sug = $sug | Get-CimInstance   # load lazy Updates property
    $ciIds = @($sug.Updates)
    if (-not $ciIds) { Write-Warning 'SUG contains no updates.'; return }

    $updates = @{}
    foreach ($chunk in 0..([math]::Floor(($ciIds.Count - 1) / 50))) {
        $slice = $ciIds[($chunk * 50)..([math]::Min($ciIds.Count, ($chunk + 1) * 50) - 1)]
        Get-CimInstance @cim -Query ("SELECT CI_ID, ArticleID, LocalizedDisplayName FROM SMS_SoftwareUpdate WHERE CI_ID IN (" + ($slice -join ',') + ")") |
            ForEach-Object { $updates[[int]$_.CI_ID] = $_ }
    }
    $members = @{}
    Get-CimInstance @cim -Query "SELECT ResourceID, Name FROM SMS_FullCollectionMembership WHERE CollectionID='$CollectionId'" | ForEach-Object { $members[[int]$_.ResourceID] = $_.Name }

    $statusMap = @{ 0 = 'Unknown'; 1 = 'NotRequired'; 2 = 'Required'; 3 = 'Installed' }
    $rows = foreach ($ci in $updates.Keys) {
        Get-CimInstance @cim -Query "SELECT ResourceID, CI_ID, Status, LastStatusCheckTime FROM SMS_UpdateComplianceStatus WHERE CI_ID = $ci" |
            Where-Object { $members.ContainsKey([int]$_.ResourceID) } |
            ForEach-Object {
                [pscustomobject]@{
                    ComputerName = $members[[int]$_.ResourceID]
                    ArticleID    = "KB$($updates[$ci].ArticleID)"
                    Update       = $updates[$ci].LocalizedDisplayName
                    Status       = $statusMap[[int]$_.Status]
                    LastChecked  = $_.LastStatusCheckTime
                }
            }
    }
    if ($Summary) {
        $rows | Group-Object ComputerName | ForEach-Object {
            $g = $_.Group
            [pscustomobject]@{
                ComputerName = $_.Name
                Required     = @($g | Where-Object Status -eq 'Required').Count
                Installed    = @($g | Where-Object Status -eq 'Installed').Count
                NotRequired  = @($g | Where-Object Status -eq 'NotRequired').Count
                Unknown      = @($g | Where-Object Status -eq 'Unknown').Count
                Compliant    = -not ($g | Where-Object { $_.Status -in 'Required', 'Unknown' })
            }
        } | Sort-Object Compliant, ComputerName
    } else { $rows }
}