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
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}' }
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
InventoryActionStatusinstance for...001inroot\ccm\invagtfirst. Do it sparingly; full inventories from a lot of machines at once can flood the site. -
Configuration baselines aren't schedule IDs. Those use
TriggerEvaluationonSMS_DesiredConfigurationinroot\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
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
It tests ping, WinRM, the CcmExec service state, and the client version. Two PowerShell 7 differences shaped how it's written:
-
Get-Service -ComputerNamewas removed in PowerShell 7, so the script queriesWin32_Serviceover CIM, which works in both editions. -
Test-Connectionuses-TargetNamein 7 (with-ComputerNamekept 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
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
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' -WhatIfcallsCCM_Applicationinroot\ccm\ClientSDK, exactly like clicking Install. The app must already be deployed to the device. -
Pending reboot report.
Get-CMPendingRebootReport -ComputerName PC01, PC02callsCCM_ClientUtilities.DetermineIfRebootPendingand checks the usual Windows registry indicators. Point it at a collection with-SiteCode/-ProviderMachineName/-CollectionIdand it reads the client state from the site instead of contacting each machine. -
Client repair.
Repair-CMClientescalates from the least invasive option (SMS_Client.RepairClient) to restartingCcmExecto a fullccmsetupreinstall. Every method is gated byShouldProcesswith 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:
-
-WhatIffirst, 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.logandPolicyEvaluator.logshow whether policy actually arrived, andAppDiscovery.log/AppEnforce.logcover 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)
tr.ee/dev-to