Skip to content

Instantly share code, notes, and snippets.

@mwallner
Last active November 21, 2023 02:42
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mwallner/9bb87e460dec1e4eec2c70217b1ec6ae to your computer and use it in GitHub Desktop.
Save mwallner/9bb87e460dec1e4eec2c70217b1ec6ae to your computer and use it in GitHub Desktop.
install all available updates via SCCM
<#
.SYNOPSIS
Install all updates available via SCCM and WAIT for the installation to finish.
.PARAMETER Computer
the computer to install updates on
.OUTPUTS
a object containing information about the installed updates and the reboot state (if a reboot is required or not)
Name Value
---- -----
result System.Management.ManagementBaseObject
updateInfo {ApprovedUpdates, PendingPatches, RebootPending}
rebootPending Boolean
.EXAMPLE
. .\Install-SCCMUpdates.ps1; Install-SCCMUpdates
dot-source the script to load the function "Install-SCCMUpdates", directly call the function afterwards
.NOTES
the target computer needs to have SCCM enabled
(this is implicitly checked by accessing the root\CCM\ClientSDK WMI namespace)
.LINK
CCM_SoftwareUpdate: https://docs.microsoft.com/en-us/sccm/develop/reference/core/clients/sdk/ccm_softwareupdate-client-wmi-class
CCM_SoftwareUpdatesManager: https://docs.microsoft.com/en-us/sccm/develop/reference/core/clients/sdk/ccm_softwareupdatesmanager-client-wmi-class
install all missing SCCM Updates (client side): https://gallery.technet.microsoft.com/scriptcenter/Install-All-Missing-8ffbd525
check for pending reboots: https://github.com/bcwilhite/PendingReboot/blob/master/Public/Test-PendingReboot.ps1
#>
function Install-SCCMUpdates {
[cmdletbinding()]
param(
$Computer = "localhost"
)
Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
$wmiCCMSDK = "root\CCM\ClientSDK"
$scriptName = $MyInvocation.MyCommand.Name
Write-Verbose "$scriptName ..."
function Test-WMIAccess {
$wmicheck = Get-WmiObject -ComputerName localhost -namespace root\cimv2 -Class Win32_BIOS -ErrorAction SilentlyContinue
if ($wmicheck) {
Write-Verbose "Test-WMIAccess - success"
return $true
}
else {
Write-Verbose "Test-WMIAccess - failure"
return $false
}
}
if (-Not (Test-WMIAccess)) {
throw "unable to contact WMI provider"
}
function Get-CCMUpdates {
[cmdletbinding()]
param (
$ComputerName
)
# Get list of all instances of CCM_SoftwareUpdate from root\CCM\ClientSDK for missing updates
Get-WmiObject -ComputerName $ComputerName -Namespace $wmiCCMSDK -Class CCM_SoftwareUpdate -Filter ComplianceState=0 -ErrorAction Stop
}
function Install-CCMUpdates {
[cmdletbinding()]
param (
$ComputerName,
$UpdateElements
)
$UpdatesReformatted = @($UpdateElements | ForEach-Object {
if ($_.ComplianceState -eq 0) {[WMI]$_.__PATH}
})
# The following is the invoke of the CCM_SoftwareUpdatesManager.InstallUpdates with our found updates
# NOTE: the command in the ArgumentList is intentional, as it flattens the Object into a System.Array for us
# The WMI method requires it in this format. (https://gallery.technet.microsoft.com/scriptcenter/Install-All-Missing-8ffbd525)
Invoke-WmiMethod -ComputerName $ComputerName -Class CCM_SoftwareUpdatesManager -Name InstallUpdates -ArgumentList (, $UpdatesReformatted) -Namespace $wmiCCMSDK
}
function Wait-ForCCMUpdatesToFinish {
[cmdletbinding()]
param(
$ComputerName
)
<#
https://docs.microsoft.com/en-us/sccm/develop/reference/core/clients/sdk/ccm_softwareupdate-client-wmi-class
EvaulationState:
0 ciJobStateNone
1 ciJobStateAvailable
2 ciJobStateSubmitted
3 ciJobStateDetecting
4 ciJobStatePreDownload
5 ciJobStateDownloading
6 ciJobStateWaitInstall
7 ciJobStateInstalling
8 ciJobStatePendingSoftReboot
9 ciJobStatePendingHardReboot
10 ciJobStateWaitReboot
11 ciJobStateVerifying
12 ciJobStateInstallComplete
13 ciJobStateError
14 ciJobStateWaitServiceWindow
15 ciJobStateWaitUserLogon
16 ciJobStateWaitUserLogoff
17 ciJobStateWaitJobUserLogon
18 ciJobStateWaitUserReconnect
19 ciJobStatePendingUserLogoff
20 ciJobStatePendingUpdate
21 ciJobStateWaitingRetry
22 ciJobStateWaitPresModeOff
23 ciJobStateWaitForOrchestration
#>
$finishStates = @(8, 9, 10, 12, 13, 19)
do {
$updates = Get-WmiObject -ComputerName $ComputerName -Class CCM_SoftwareUpdate -Namespace $wmiCCMSDK -Filter ComplianceState=0
$updates | Foreach-Object {
Write-Progress -Activity $_.Name -PercentComplete $_.PercentComplete
Start-Sleep -Seconds 1
}
Start-Sleep -Seconds 1
Write-Host "[$($updates.PercentComplete)]% - EvaluationState [$($updates.EvaluationState)]"
$stateFinished = $true
foreach ($state in $updates.EvaluationState) {
if (-Not ($finishStates -contains $state)) {
$stateFinished = $false
break;
}
}
# yup, $updates.PercentComplete is an array, but "-ne" will acts as a filter function
} while (($updates.PercentComplete -ne 100) -And (-Not $stateFinished))
}
$updates = Get-CCMUpdates -ComputerName $Computer
$updates | ForEach-Object {
Write-Verbose $_
}
$updateProps = @{
ApprovedUpdates = ($updates | Measure-Object).Count
PendingPatches = ($updates | Where-Object { $updates.EvaluationState -ne 8 } | Measure-Object).Count
RebootPending = ($updates | Where-Object { $updates.EvaluationState -eq 8 } | Measure-Object).Count
}
Write-Host " ApprovedUpdates: $($updateProps.ApprovedUpdates) "
Write-Host " PendingPatches: $($updateProps.PendingPatches) "
Write-Host " RebootPending: $($updateProps.RebootPending) "
$res = @{
updateInfo = $updateProps
result = $null
rebootPending = $false
}
if ($updateProps.PendingPatches -gt 0) {
try {
$res.result = Install-CCMUpdates -ComputerName $Computer -UpdateElements $updates
Wait-ForCCMUpdatesToFinish -ComputerName $Computer
}
catch {
throw "failed to install updates."
}
}
else {
Write-Host " > no updates pending < " -ForegroundColor Green
}
if ($res.result) {
Write-Verbose $res.result
}
<#
Test-PendingReboot https://github.com/bcwilhite/PendingReboot/blob/master/Public/Test-PendingReboot.ps1
.SYNOPSIS
Test the pending reboot status on a local and/or remote computer.
.NOTES
Author: Brian Wilhite
Email: bcwilhite (at) live.com
#>
function Test-PendingReboot {
[CmdletBinding()]
param(
[Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("CN", "Computer")]
[String[]]
$ComputerName = $env:COMPUTERNAME,
[Parameter()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.CredentialAttribute()]
$Credential,
[Parameter()]
[Switch]
$Detailed,
[Parameter()]
[Switch]
$SkipConfigurationManagerClientCheck,
[Parameter()]
[Switch]
$SkipPendingFileRenameOperationsCheck
)
process {
foreach ($computer in $ComputerName) {
try {
$invokeWmiMethodParameters = @{
Namespace = 'root/default'
Class = 'StdRegProv'
Name = 'EnumKey'
ComputerName = $computer
ErrorAction = 'Stop'
}
$hklm = [UInt32] "0x80000002"
if ($PSBoundParameters.ContainsKey('Credential')) {
$invokeWmiMethodParameters.Credential = $Credential
}
## Query the Component Based Servicing Reg Key
$invokeWmiMethodParameters.ArgumentList = @($hklm, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\')
$registryComponentBasedServicing = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames -contains 'RebootPending'
## Query WUAU from the registry
$invokeWmiMethodParameters.ArgumentList = @($hklm, 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\')
$registryWindowsUpdateAutoUpdate = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames -contains 'RebootRequired'
## Query JoinDomain key from the registry - These keys are present if pending a reboot from a domain join operation
$invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Services\Netlogon')
$registryNetlogon = (Invoke-WmiMethod @invokeWmiMethodParameters).sNames
$pendingDomainJoin = ($registryNetlogon -contains 'JoinDomain') -or ($registryNetlogon -contains 'AvoidSpnSet')
## Query ComputerName and ActiveComputerName from the registry and setting the MethodName to GetMultiStringValue
$invokeWmiMethodParameters.Name = 'GetMultiStringValue'
$invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\', 'ComputerName')
$registryActiveComputerName = Invoke-WmiMethod @invokeWmiMethodParameters
$invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\', 'ComputerName')
$registryComputerName = Invoke-WmiMethod @invokeWmiMethodParameters
$pendingComputerRename = $registryActiveComputerName -ne $registryComputerName -or $pendingDomainJoin
## Query PendingFileRenameOperations from the registry
if (-not $PSBoundParameters.ContainsKey('SkipPendingFileRenameOperationsCheck')) {
$invokeWmiMethodParameters.ArgumentList = @($hklm, 'SYSTEM\CurrentControlSet\Control\Session Manager\', 'PendingFileRenameOperations')
$registryPendingFileRenameOperations = (Invoke-WmiMethod @invokeWmiMethodParameters).sValue
$registryPendingFileRenameOperationsBool = [bool]$registryPendingFileRenameOperations
}
## Query ClientSDK for pending reboot status, unless SkipConfigurationManagerClientCheck is present
if (-not $PSBoundParameters.ContainsKey('SkipConfigurationManagerClientCheck')) {
$invokeWmiMethodParameters.NameSpace = 'ROOT\ccm\ClientSDK'
$invokeWmiMethodParameters.Class = 'CCM_ClientUtilities'
$invokeWmiMethodParameters.Name = 'DetermineifRebootPending'
$invokeWmiMethodParameters.Remove('ArgumentList')
try {
$sccmClientSDK = Invoke-WmiMethod @invokeWmiMethodParameters
$systemCenterConfigManager = $sccmClientSDK.ReturnValue -eq 0 -and ($sccmClientSDK.IsHardRebootPending -or $sccmClientSDK.RebootPending)
}
catch {
$systemCenterConfigManager = $null
Write-Verbose -Message ($script:localizedData.invokeWmiClientSDKError -f $computer)
}
}
$isRebootPending = $registryComponentBasedServicing -or `
$pendingComputerRename -or `
$pendingDomainJoin -or `
$registryPendingFileRenameOperationsBool -or `
$systemCenterConfigManager -or `
$registryWindowsUpdateAutoUpdate
if ($PSBoundParameters.ContainsKey('Detailed')) {
[PSCustomObject]@{
ComputerName = $computer
ComponentBasedServicing = $registryComponentBasedServicing
PendingComputerRenameDomainJoin = $pendingComputerRename
PendingFileRenameOperations = $registryPendingFileRenameOperationsBool
PendingFileRenameOperationsValue = $registryPendingFileRenameOperations
SystemCenterConfigManager = $systemCenterConfigManager
WindowsUpdateAutoUpdate = $registryWindowsUpdateAutoUpdate
IsRebootPending = $isRebootPending
}
}
else {
[PSCustomObject]@{
ComputerName = $computer
IsRebootPending = $isRebootPending
}
}
}
catch {
Write-Verbose "$Computer`: $_"
}
}
}
}
$res.rebootPending = (Test-PendingReboot -ComputerName $Computer).IsRebootPending
if ($res.rebootPending) {
Write-Host " > REBOOT PENDING < " -ForegroundColor Yellow
}
Write-Output $res
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment