DEV Community

Anthony Buhnerkemper
Anthony Buhnerkemper

Posted on

Automating Configuration Manager (SCCM) client actions with PowerShell

If you've supported Configuration Manager for any length of time, you know the routine: someone deploys something, a user can't see it in Software Center, and the first answer is "run Machine Policy Retrieval and wait a few minutes." Doing that by remoting into each machine and clicking through the Configuration Manager control panel applet doesn't scale past a handful of devices.

Every one of those buttons is just a WMI method call, so PowerShell can do it for one machine or five thousand. The scripts in this post are in my open-source configmgr-powershell-toolkit on GitHub, also available as a module on the PowerShell Gallery:

Install-Module ConfigMgrAdminToolkit -Scope CurrentUser
Enter fullscreen mode Exit fullscreen mode

How client actions work

Each action in the client's Actions tab maps to a schedule ID, a GUID. You fire it with the static TriggerSchedule method on the SMS_Client class in the client's root\ccm namespace:

Invoke-CimMethod -ComputerName PC01 -Namespace root\ccm -ClassName SMS_Client `
    -MethodName TriggerSchedule -Arguments @{ sScheduleID = '{00000000-0000-0000-0000-000000000021}' }
Enter fullscreen mode Exit fullscreen mode

That's the whole trick. The rest is knowing the IDs and handling many machines safely.

The schedule IDs worth memorizing

Action Schedule ID
Hardware Inventory {00000000-0000-0000-0000-000000000001}
Software Inventory {00000000-0000-0000-0000-000000000002}
Discovery Data Record (heartbeat) {00000000-0000-0000-0000-000000000003}
Machine Policy Retrieval {00000000-0000-0000-0000-000000000021}
Machine Policy Evaluation {00000000-0000-0000-0000-000000000022}
Software Updates Assignments Evaluation {00000000-0000-0000-0000-000000000108}
State Message Refresh {00000000-0000-0000-0000-000000000111}
Software Update Scan {00000000-0000-0000-0000-000000000113}
Software Update Deployment Evaluation {00000000-0000-0000-0000-000000000114}
Application Deployment Evaluation {00000000-0000-0000-0000-000000000121}

A few gotchas:

  • User policy (...026 / ...027) is tied to the logged-on user's SID. Triggering it remotely as an admin refreshes your policy, not the user's, so I generally skip it.
  • Full hardware inventory requires deleting the InventoryActionStatus instance for ...001 in root\ccm\invagt first. Do it sparingly; full inventories from a lot of machines at once can flood the site.
  • Configuration baselines aren't schedule IDs. Those use TriggerEvaluation on SMS_DesiredConfiguration in root\ccm\dcm.

Friendly names instead of GUIDs: Invoke-CMClientAction

Nobody wants to type GUIDs, so Invoke-CMClientAction maps friendly names to IDs, takes pipeline input, and supports -WhatIf:

Invoke-CMClientAction -ComputerName PC01, PC02 -Action MachinePolicyRetrieval, MachinePolicyEvaluation -WhatIf

# Raw ID still works for anything not in the list
Invoke-CMClientAction -ComputerName PC01 -ScheduleId '{00000000-0000-0000-0000-000000000113}'

# Pipeline from a file
Get-Content .\pcs.txt | Invoke-CMClientAction -Action ApplicationDeploymentEvaluation
Enter fullscreen mode Exit fullscreen mode

It uses a CIM session over WSMan by default. Add -Protocol Dcom for older machines where WinRM isn't enabled. You need local admin on the target either way.

Check before you trigger: Test-CMClientConnectivity

Half the "it didn't work" reports I get are machines that are off, unreachable, or have a broken client. Before a big run I check:

Get-Content .\pcs.txt | Test-CMClientConnectivity | Format-Table
Enter fullscreen mode Exit fullscreen mode

It tests ping, WinRM, the CcmExec service state, and the client version. Two PowerShell 7 differences shaped how it's written:

  • Get-Service -ComputerName was removed in PowerShell 7, so the script queries Win32_Service over CIM, which works in both editions.
  • Test-Connection uses -TargetName in 7 (with -ComputerName kept as an alias) and returns different objects.

Also remember that "Active" in the console's client activity view only means the client did something within the activity threshold (seven days by default). It doesn't mean the machine is online right now. That's why site-side health (Get-CMClientHealthReport) and live connectivity are separate tools in the kit.

Scaling to a collection: Invoke-CMClientPolicyRefreshBulk

For a whole collection, I use Invoke-CMClientPolicyRefreshBulk. It accepts names, a text file, or a collection ID resolved through the SMS Provider, and it has two modes:

# Direct: CIM TriggerSchedule on each client, run in parallel on PowerShell 7
Invoke-CMClientPolicyRefreshBulk -SiteCode HOU -ProviderMachineName cm01 -CollectionId HOU00042 -Mode Direct -ThrottleLimit 32 -WhatIf

# Notification: ask the site to push it over the client notification channel
Invoke-CMClientPolicyRefreshBulk -SiteCode HOU -ProviderMachineName cm01 -CollectionId HOU00042 -Mode Notification
Enter fullscreen mode Exit fullscreen mode

Direct mode is immediate and gives a per-machine result, but needs remoting and local admin on every endpoint. On PowerShell 7 it fans out with ForEach-Object -Parallel. On 5.1, Invoke-Command against a list or an array of CIM sessions gets you similar parallelism.

Notification mode uses the site's own cmdlet:

Invoke-CMClientNotification -DeviceCollectionId 'HOU00042' -ActionType DownloadComputerPolicy
Invoke-CMClientNotification -DeviceName 'PC01' -ActionType RequestScanForUpdate
Enter fullscreen mode Exit fullscreen mode

This goes over the client notification channel through the management point, so you don't need firewall rules for remote management to every endpoint, and the site throttles the work. For large batches, this is what I reach for first. The available -ActionType values depend on your version; check Get-Help Invoke-CMClientNotification -Parameter ActionType. The older Invoke-CMClientAction site cmdlet is deprecated in favor of it (not to be confused with my script of the same name, which talks to clients directly).

Related client-side actions

The same pattern of calling a client WMI class works for a few other jobs I automate:

  • Install an app from Software Center remotely. Invoke-CMAppInstallOnClient -ComputerName PC01 -ApplicationName '7-Zip 24.08 x64' -WhatIf calls CCM_Application in root\ccm\ClientSDK, exactly like clicking Install. The app must already be deployed to the device.
  • Pending reboot report. Get-CMPendingRebootReport -ComputerName PC01, PC02 calls CCM_ClientUtilities.DetermineIfRebootPending and checks the usual Windows registry indicators. Point it at a collection with -SiteCode / -ProviderMachineName / -CollectionId and it reads the client state from the site instead of contacting each machine.
  • Client repair. Repair-CMClient escalates from the least invasive option (SMS_Client.RepairClient) to restarting CcmExec to a full ccmsetup reinstall. Every method is gated by ShouldProcess with high confirm impact.

Safe practice

Client actions feel harmless, and a single policy refresh is. But a full inventory, a software update evaluation, or an app install across a large collection can put real load on your management points, distribution points, and network. The rules I follow:

  • -WhatIf first, and count your targets before anything else.
  • Pilot collections first, then broader rings.
  • Never target All Systems on a whim. Build a collection that means what you intend.
  • Prefer client notification for large batches, so the site throttles for you.
  • Check the logs on the client (C:\Windows\CCM\Logs). PolicyAgent.log and PolicyEvaluator.log show whether policy actually arrived, and AppDiscovery.log / AppEnforce.log cover apps.

Get the code

Everything here is MIT-licensed, with a longer guide in the repo covering the ConfigurationManager module, SMS Provider queries, applications vs. packages, content distribution, software updates, Run Scripts, CMPivot and RBAC:

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •
You need to verify your account.
Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to