Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Check MECM Software Update Deployment Status with PowerShell

One object per update deployment with targeted, compliant, failed and unknown counts, so you can spot the deployments that need you.

AT A GLANCEGet-CMUpdateDeploymentStatus.ps1
What it does
Pulls the summary numbers ConfigMgr keeps for every software update deployment and returns them as objects with a compliance percentage. Filter by group, collection or age, or ask for only the ones with problems.
Requires
  • Windows PowerShell 5.1
  • Configuration Manager console installed, for the ConfigurationManager module
Permissions
Read-only access to software update deployments. Read-only Analyst is plenty.
Runs on
Any machine with the Configuration Manager console
Tested
Parse-checked and dry-run with mocked cmdlets in PowerShell 7.4

The console's Monitoring workspace will show you deployment status, one deployment at a time, with a pie chart and a lot of clicking. That's fine when you're looking at one thing. It's miserable on a Monday morning when you want to know which of last month's twenty deployments are still limping along.

ConfigMgr already keeps a summary row for every deployment: how many machines were targeted, how many are compliant, how many failed, how many haven't said anything yet. Get-CMDeployment hands you all of it. This script keeps the software update ones, works out a percentage, and gives you objects you can sort, filter, or drop into a CSV.

The old version of this post listed update groups, packages and "successful deployments" in three separate tables, and the success filter checked a property that doesn't exist, so it never actually filtered anything. This rewrite focuses on the question people really ask: how are my deployments doing?

Get-CMUpdateDeploymentStatus.ps1Download
<#
.SYNOPSIS
    Shows how every software update deployment is doing: targeted, compliant, failed, unknown.
.DESCRIPTION
    Reads the deployment summary data ConfigMgr (MECM) keeps for each deployment and returns one
    object per software update deployment, with a compliance percentage. Filter by update group
    name, collection, or age, or ask for only the deployments that need attention. Read-only.
.PARAMETER SiteCode
    Your three-character site code, for example ABC.
.PARAMETER ProviderMachineName
    The SMS Provider server. Defaults to this computer.
.PARAMETER GroupNameLike
    Wildcard filter on the software update group name. Default: all.
.PARAMETER CollectionNameLike
    Wildcard filter on the target collection name. Default: all.
.PARAMETER Days
    Only deployments created in the last N days. 0 means no limit. Default: 90.
.PARAMETER Threshold
    With -NeedsAttention, deployments below this compliance percentage are returned. Default: 95.
.PARAMETER NeedsAttention
    Return only deployments with errors or compliance under the threshold.
.EXAMPLE
    .\Get-CMUpdateDeploymentStatus.ps1 -SiteCode ABC -NeedsAttention
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory)][ValidatePattern('^[A-Za-z0-9]{3}$')][string]$SiteCode,
    [string]$ProviderMachineName = $env:COMPUTERNAME,
    [string]$GroupNameLike = '*',
    [string]$CollectionNameLike = '*',
    [ValidateRange(0, 3650)][int]$Days = 90,
    [ValidateRange(0, 100)][int]$Threshold = 95,
    [switch]$NeedsAttention
)

$ErrorActionPreference = 'Stop'

if (-not (Get-Module ConfigurationManager)) {
    if (-not $env:SMS_ADMIN_UI_PATH) { throw 'The Configuration Manager console is not installed on this machine.' }
    Import-Module (Join-Path $env:SMS_ADMIN_UI_PATH '..\ConfigurationManager.psd1')
}
if (-not (Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
    New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName | Out-Null
}

$since = if ($Days -gt 0) { (Get-Date).AddDays(-$Days) } else { [datetime]::MinValue }

Push-Location "$($SiteCode):\"
try {
    # FeatureType 5 = software updates. Everything else (apps, task sequences, baselines) is skipped.
    $deployments = Get-CMDeployment | Where-Object {
        $_.FeatureType -eq 5 -and
        $_.SoftwareName -like $GroupNameLike -and
        $_.CollectionName -like $CollectionNameLike -and
        $_.DeploymentTime -ge $since
    }

    $results = foreach ($d in $deployments) {
        $targeted = [int]$d.NumberTargeted
        $percent  = if ($targeted -gt 0) { [math]::Round(100 * $d.NumberSuccess / $targeted, 1) } else { $null }

        [pscustomobject]@{
            UpdateGroup  = $d.SoftwareName
            Collection   = $d.CollectionName
            Created      = $d.DeploymentTime
            Deadline     = $d.EnforcementDeadline
            Targeted     = $targeted
            Compliant    = [int]$d.NumberSuccess
            InProgress   = [int]$d.NumberInProgress
            Failed       = [int]$d.NumberErrors
            Unknown      = [int]$d.NumberUnknown
            PercentOK    = $percent
            LastSummary  = $d.SummarizationTime
            DeploymentID = $d.DeploymentID
        }
    }

    if ($NeedsAttention) {
        $results = $results | Where-Object { $_.Failed -gt 0 -or ($null -ne $_.PercentOK -and $_.PercentOK -lt $Threshold) }
    }

    $results | Sort-Object Created -Descending
}
finally {
    Pop-Location
}

Parameters

ParameterTypeDefaultWhat it's for
-SiteCodestring—Your three-character site code, like ABC.
-ProviderMachineNamestring$env:COMPUTERNAMEThe SMS Provider server.
-GroupNameLikestring*Wildcard filter on the software update group name, like '2026-09*'.
-CollectionNameLikestring*Wildcard filter on the target collection.
-Daysint90Only deployments created in the last N days. Use 0 for everything.
-Thresholdint95The compliance percentage that counts as healthy, used with -NeedsAttention.
-NeedsAttentionswitch—Only return deployments with failures or compliance below the threshold.

Run it

Everything from the last 90 days, newest first.

.\Get-CMUpdateDeploymentStatus.ps1 -SiteCode ABC | Format-Table UpdateGroup, Collection, Targeted, Compliant, Failed, PercentOK

Just the deployments that need a look.

.\Get-CMUpdateDeploymentStatus.ps1 -SiteCode ABC -NeedsAttention

How this month's groups are doing on servers, saved for the change meeting.

.\Get-CMUpdateDeploymentStatus.ps1 -SiteCode ABC -GroupNameLike '2026-09*' -CollectionNameLike '*Server*' | Export-Csv .\server-patching.csv -NoTypeInformation

Anything with more than 20 machines that haven't reported at all.

.\Get-CMUpdateDeploymentStatus.ps1 -SiteCode ABC -Days 30 | Where-Object Unknown -gt 20

What you'll see

Example outputvalues are illustrative
UpdateGroup                  Collection            Targeted Compliant Failed Unknown PercentOK
-----------                  ----------            -------- --------- ------ ------- ---------
2026-09 Workstation Updates  Pilot - Workstations        50        41      2       3        82
2026-09 Server Updates       Servers - Group A           64        60      0       4      93.8
2026-08 Workstation Updates  All Workstations           812       797      1      14      98.2
2026-08 Server Updates       Servers - Group B           71        71      0       0       100

How it works

  1. Connect to the site. It loads the ConfigurationManager module from the console install and maps the site drive if needed.
  2. Get every deployment summary. Get-CMDeployment returns one summary row per deployment. The script keeps the software update ones (FeatureType 5) that match your name, collection and age filters.
  3. Do the math. For each one it builds an object with the counts ConfigMgr tracks (targeted, compliant, in progress, failed, unknown) and a PercentOK rounded to one decimal. A deployment with nothing targeted gets $null instead of a divide-by-zero.
  4. Filter if asked. With -NeedsAttention, only deployments with at least one failure or compliance under -Threshold come back.
  5. Sort newest first and return objects, not a table, so you can pipe them wherever.

Take it further

  • Email the problem list. Schedule it with -NeedsAttention and mail the results every Monday. Send-MailMessage is deprecated, so Graph's Send-MgUserMail is the better route now.
  • Drill into failures. The summary tells you which deployment is unhappy. Open it under Monitoring > Deployments in the console to see which machines failed and the error codes behind them.
  • Pair it with the deployment script. New-CMTopUpdateDeployment pushes the most-needed updates to a pilot; this tells you how the pilot went.

Things that'll trip you up

  • The numbers are summarized, not live. These counts come from ConfigMgr's summarization, which runs on a schedule. The LastSummary column tells you how old they are. If you need fresher numbers, run summarization from the deployment in the console first.
  • Unknown isn't the same as failed. Unknown usually means the client hasn't sent state messages yet, or it's offline, or it's broken. A high Unknown count on an old deployment is worth chasing, because those machines might not be getting anything at all.
  • FeatureType 5 means software updates. Get-CMDeployment returns every kind of deployment. The script keeps FeatureType 5 and ignores applications, packages, task sequences and baselines.
  • Targeted is a snapshot too. If machines joined or left the collection after the deployment, the targeted count moves. A percentage that drops for no obvious reason is often just new machines arriving.