SCCM Patch-Management Tasks (client side)
Software Update Management with System Center Configuration Manager, can become tricky if there are many different Schedules and Exceptions. One way to granular control Software Update deployments is by using Client-Side Scripts (e.g. PowerShell). It's still SCCM that is responsible to download and install the updates, but the trigger is an "external" Script.
the following Workflow is an example of a Server-Patch runbook:
There are different options to trigger such a runbook/script on a Computer:
- SMA / Orchestrator ( e.g. in combination with SCSM Service Request )
- Configuration Item/Baseline in SCCM
- SCCM Package / Task-Sequence
- Scheduled Task
- ...
But on the SCCM Agent Side there are always the same actions to trigger... Let's make some examples:
refresh Machine Policy:
Enforce the Agent to get the latest Policies from the Site Server.
([wmiclass]'ROOT\ccm:SMS_Client').TriggerSchedule('{00000000-0000-0000-0000-000000000021}')
Get last SW Update Scan Time:
Get the Date/Time of the last WindowsUpdate Scan.
get-wmiobject -query "SELECT * FROM CCM_UpdateStatus" -namespace "root\ccm\SoftwareUpdates\UpdatesStore" | % { if($_.ScanTime -gt $ScanTime) { $ScanTime = $_.ScanTime } }; $LastScan = ([System.Management.ManagementDateTimeConverter]::ToDateTime($ScanTime)); $LastScan
Trigger an Update-Scan if last Scan-Time is older than 1 hour
Only trigger an WindowsUpdate Scan if the last Scan-Time is older than 1 hour.
if(((get-date) - $LastScan).Hours -ge 1) { ([wmiclass]'ROOT\ccm:SMS_Client').TriggerSchedule('{00000000-0000-0000-0000-000000000113}')}
Install Updates
It's often a requirement to exclude Updates or to specify which Updates to install (in this example, two Updates with ArticleID 2461484 and 300483 will be installed).
get-wmiobject -query "SELECT * FROM CCM_SoftwareUpdate" -namespace "ROOT\ccm\ClientSDK" | ? ArticleID -in @('2461484', '3000483') | % { ([wmiclass]'ROOT\ccm\ClientSDK:CCM_SoftwareUpdatesManager').InstallUpdates($_)}; sleep 30
If you want to exclude updates, you have to change 'ArticleID -in ' into 'ArticleID -notin '
You can now modify this script to get the exclusions from e.g. a Web-Service, SCCM Variable, Registry or File if you want to manage exclusions for specific Computers...
Another option to trigger all assigned Updates:
([wmiclass]'ROOT\ccm\ClientSDK:CCM_SoftwareUpdatesManager').InstallUpdates([System.Management.ManagementObject[]] (get-wmiobject -query 'SELECT * FROM CCM_SoftwareUpdate' -namespace 'ROOT\ccm\ClientSDK'))
or if you want to install all mandatory Updates:
([wmiclass]'ROOT\ccm\ClientSDK:CCM_SoftwareUpdatesManager').InstallUpdates()
Detect running installations:
To get a propper state, we have to know if all installations are done.
$CCMUpdate = get-wmiobject -query "SELECT * FROM CCM_SoftwareUpdate" -namespace "ROOT\ccm\ClientSDK"
if(@($CCMUpdate | where { $_.EvaluationState -eq 2 -or $_.EvaluationState -eq 3 -or $_.EvaluationState -eq 4 -or $_.EvaluationState -eq 5 -or $_.EvaluationState -eq 6 -or $_.EvaluationState -eq 7 -or $_.EvaluationState -eq 11 }).length -ne 0) { $true } else { $false }
Detect pending reboot:
We only have to reboot if a reboot is required. The following script does check the reboot state from the SCCM Agent and it also verifies if there are pending file rename operations, which will be handled at reboot.
$Reboot = $false
$Reboot = $Reboot -or ([wmiclass]'ROOT\ccm\ClientSDK:CCM_ClientUtilities').DetermineIfRebootPending().RebootPending
$Reboot = $Reboot -or ([wmiclass]'ROOT\ccm\ClientSDK:CCM_ClientUtilities').DetermineIfRebootPending().IsHardRebootPending
$Reboot = $Reboot -or {if(@(((Get-ItemProperty("HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager")).$("PendingFileRenameOperations")) | where { $_ }).length -ne 0) { $true } else { $false }}
$Reboot = $Reboot -or {if(@($CCMUpdate | where { $_.EvaluationState -eq 8 -or $_.EvaluationState -eq 9 -or $_.EvaluationState -eq 10 }).length -ne 0) { $true } else { $false }}
It can be helpfull to also check the last reboot Time bfore you enforce a reboot... just to prevent reboot loops...
Count missing updates
At the end, you should not have missing updates.
@(get-wmiobject -query "SELECT * FROM CCM_SoftwareUpdate WHERE ComplianceState = 0" -namespace "ROOT\ccm\ClientSDK").count
If you have excluded some updates, make sure that these updates are getting excluded from the final count.
This is not a complete solution, but it may help to create Scripts that reflects your needs for Software Update Management...