<#
.SYNOPSIS
Installs every missing software update currently visible in Software Center.
.DESCRIPTION
Intended for use with Configuration Manager's Run Scripts feature. The
script queries the local ConfigMgr Client SDK for deployed, applicable,
missing updates that are visible in Software Center, then submits all of
them to CCM_SoftwareUpdatesManager in one installation request.
Installation continues asynchronously after this script finishes. Reboot
and maintenance-window behavior remain controlled by the ConfigMgr
deployment and client settings; this script does not force a reboot.
.NOTES
Run context: Local System (the normal SCCM Run Scripts context)
PowerShell: Windows PowerShell 5.1
#>
$ErrorActionPreference = 'Stop'
$Namespace = 'root\ccm\ClientSDK'
$UpdateClass = 'CCM_SoftwareUpdate'
$MaximumRows = 25
$EvaluationStateNames = @{
0 = 'None'
1 = 'Available'
2 = 'Submitted'
3 = 'Detecting'
4 = 'Pre-download'
5 = 'Downloading'
6 = 'Waiting to install'
7 = 'Installing'
8 = 'Pending soft reboot'
9 = 'Pending hard reboot'
10 = 'Waiting for reboot'
11 = 'Verifying'
12 = 'Install complete'
13 = 'Error'
14 = 'Waiting for service window'
15 = 'Waiting for user logon'
16 = 'Waiting for user logoff'
17 = 'Waiting for job user logon'
18 = 'Waiting for user reconnect'
19 = 'Pending user logoff'
20 = 'Pending update'
21 = 'Waiting to retry'
22 = 'Waiting for presentation mode'
23 = 'Waiting for orchestration'
}
function ConvertTo-HexErrorCode {
param(
[Parameter(Mandatory = $true)]
[UInt32]$Code
)
return ('0x{0:X8}' -f $Code)
}
function Get-DisplayName {
param(
[Parameter(Mandatory = $true)]
[System.Management.ManagementBaseObject]$Update
)
$name = [string]$Update.Name
$name = $name -replace '[\r\n\t]+', ' '
if ($name.Length -gt 72) {
$name = $name.Substring(0, 69) + '...'
}
return $name
}
try {
# CCM_SoftwareUpdate enumerates deployed, applicable updates that are not
# installed. ComplianceState 0 means missing; UserUIExperience means the
# update is visible in Software Center.
$updates = @(
Get-WmiObject -Namespace $Namespace `
-Query "SELECT * FROM $UpdateClass WHERE ComplianceState = 0 AND UserUIExperience = TRUE"
)
if ($updates.Count -eq 0) {
Write-Output @"
Computer: $env:COMPUTERNAME
Status: No missing updates are currently listed in Software Center.
Updates submitted: 0
"@
return
}
[System.Management.ManagementObject[]]$updateObjects = $updates
$manager = [WmiClass]"\\.\${Namespace}:CCM_SoftwareUpdatesManager"
$installResult = $manager.InstallUpdates($updateObjects)
$returnCode = [UInt32]$installResult.ReturnValue
if ($returnCode -ne 0) {
throw "CCM_SoftwareUpdatesManager.InstallUpdates returned $(ConvertTo-HexErrorCode -Code $returnCode)."
}
# Give the ConfigMgr client a moment to change the initial evaluation state.
Start-Sleep -Seconds 5
$currentUpdates = @(
Get-WmiObject -Namespace $Namespace -Class $UpdateClass
)
$currentById = @{}
foreach ($currentUpdate in $currentUpdates) {
$currentById[[string]$currentUpdate.UpdateID] = $currentUpdate
}
$lines = @()
$lines += "Computer: $env:COMPUTERNAME"
$lines += 'Status: Installation request submitted successfully.'
$lines += "Updates submitted: $($updates.Count)"
$lines += 'Reboot: Not forced by this script; ConfigMgr deployment settings apply.'
$lines += ''
$lines += ('{0,-12} {1,-28} {2,-5} {3}' -f 'KB', 'Initial state', 'Done', 'Update')
$lines += ('-' * 122)
$reported = 0
foreach ($update in ($updates | Sort-Object ArticleID, Name)) {
if ($reported -ge $MaximumRows) {
break
}
$kb = if ([string]::IsNullOrWhiteSpace([string]$update.ArticleID)) {
'-'
}
else {
'KB' + [string]$update.ArticleID
}
$current = $currentById[[string]$update.UpdateID]
if ($null -eq $current) {
$stateName = 'Completed/not required'
$percent = '100%'
}
else {
$stateNumber = [Int32]$current.EvaluationState
$stateName = if ($EvaluationStateNames.ContainsKey($stateNumber)) {
$EvaluationStateNames[$stateNumber]
}
else {
"Unknown ($stateNumber)"
}
$percent = '{0}%' -f [Int32]$current.PercentComplete
if ([UInt32]$current.ErrorCode -ne 0) {
$stateName = "$stateName / $(ConvertTo-HexErrorCode -Code ([UInt32]$current.ErrorCode))"
}
}
if ($stateName.Length -gt 28) {
$stateName = $stateName.Substring(0, 25) + '...'
}
$lines += ('{0,-12} {1,-28} {2,-5} {3}' -f `
$kb, $stateName, $percent, (Get-DisplayName -Update $update))
$reported++
}
if ($updates.Count -gt $MaximumRows) {
$lines += ''
$lines += "$($updates.Count - $MaximumRows) additional update(s) were submitted but omitted from this SCCM output."
}
Write-Output ($lines -join [Environment]::NewLine)
}
catch {
$message = $_.Exception.Message -replace '[\r\n]+', ' '
Write-Output @"
Computer: $env:COMPUTERNAME
Status: Failed to submit Software Center updates.
Error: $message
"@
exit 1
}
Comments
Post a Comment