Introduction
It has been a long wish of mine to create a PowerShell script that runs all the SCCM Client actions on a batch of computers. Of course, there are already tools available that do the trick, but not always in a convenient way. For example, you cannot run the action 'Application Global Evaluation Task' on remote computers.
The requirements
There are some must-haves:
- The script should be run against computer and user collections
- The script should be run against computers that match a filter
- No need to install all kinds of PowerShell modules to use this script
- All the actions must be run, including those that can only run under user context
- Avoid issues when AppLocker is in place.
The approach
It would be nice if the queries could be run against the SCCM / MECM database. But then, the user running the script must have SELECT rights on the database. If you do not have them, then the script is useless. So, I decided to use WMI queries instead. It is not the quickest way, but it is the best approach in this case.
WMI
Let's review this example: you entered a user-based collection name.
The starting point are the CollectionType, CollectionID, Name and MemberClassName from the class SMS_Collection.
Get-CimInstance @WMIParam -ClassName SMS_Collection | Select-Object -Property CollectionType, CollectionID,Name,MemberClassName
CollectionType CollectionID Name MemberClassName
-------------- ------------ ---- ---------------
2 SMS00001 All Systems SMS_CM_RES_COLL_SMS00001
1 SMS00002 All Users SMS_CM_RES_COLL_SMS00002
1 SMS00003 All User Groups SMS_CM_RES_COLL_SMS00003
1 SMS00004 All Users and User Groups SMS_CM_RES_COLL_SMS00004
0 SMSOTHER All Custom Resources SMS_CM_RES_COLL_SMSOTHER
2 SMS000US All Unknown Computers SMS_CM_RES_COLL_SMS000US
2 SMS000PS All Provisioning Devices SMS_CM_RES_COLL_SMS000PS
2 SMSDM001 All Mobile Devices SMS_CM_RES_COLL_SMSDM001
2 SMSDM003 All Desktop and Server Clients SMS_CM_RES_COLL_SMSDM003
2 TST00014 Managed Domain Computers SMS_CM_RES_COLL_TST00014
1 TST00015 All Test User Accounts SMS_CM_RES_COLL_TST00015
2 TST00016 Managed Domain Computers for Feature Releases SMS_CM_RES_COLL_TST00016
2 SMS000KM Co-management Eligible Devices SMS_CM_RES_COLL_SMS000KM
1 TST00017 Admins SMS_CM_RES_COLL_TST00017
1 TST00018 Remove Firefox SMS_CM_RES_COLL_TST00018
1 TST00019 Demo - ProjectLibre SMS_CM_RES_COLL_TST00019
1 TST0001A Demo - NotepadPlusPlus SMS_CM_RES_COLL_TST0001A
1 TST0001B Demo - Adobe Acrobat DC Pro SMS_CM_RES_COLL_TST0001B
If the CollectionType is 1, then the collection is user-based. If the CollectionType is 2, then the collection is device-based.
But... a user-based collection can exist of users or groups. So, we have to know each member. That information is stored in the field SMSID of the class SMS_CM_RES_COLL_<CollectionID>.
Each member can be a group resource or user resource.
Get-CimInstance @WMIParam -Class 'SMS_CM_RES_COLL_TST0001B' | Select-Object -Property SMSID
SMSID
-----
DEMO\Demo - Adobe Acrobat DC Pro
Get-CimInstance @WMIParam -Class 'SMS_CM_RES_COLL_TST0001A' | Select-Object -Property SMSID
SMSID
-----
DEMO\Test1
DEMO\Test4
DEMO\Test3
DEMO\Test5
DEMO\Test2
DEMO\Test6
DEMO\Test43
There is one (simple) way to find this: ADSI. Use the command:
([adsi]"WinNT://DEMO/Demo - Adobe Acrobat DC Pro").Properties.GroupType
2
([adsi]"WinNT://DEMO/Test1").Properties.GroupType
If the result is a group, we can find each user with the query: select SMS_R_User.UniqueUserName from SMS_R_User where SMS_R_User.UserGroupName LIKE '%Demo - Adobe Acrobat DC Pro'
Get-CimInstance @WMIParam -query "select SMS_R_User.UniqueUserName from SMS_R_User where SMS_R_User.UserGroupName LIKE '%Demo - Adobe Acrobat DC Pro'" | Select-Object -Property UniqueUserName
UniqueUserName
--------------
DEMO\Test1
DEMO\Test17
DEMO\Test93
Now, we have all the users we want to target.
Association user and computer
The next step is the association between the user and the computer.
That can be done via the WMI query
Get-CimInstance @WMIParam -ClassName SMS_CombinedDeviceResources -Filter "Name = 'VM01-W10-22H2'" | Select-Object -Property Name, CurrentLogonUser,LastLogonUser
Name CurrentLogonUser LastLogonUser
---- ---------------- -------------
VM01-W10-22H2 DEMO\Test7 defaultuser0
Or the other way around if you know the user:
Get-CimInstance @WMIParam -ClassName SMS_CombinedDeviceResources -Filter "CurrentLogonUser = 'DEMO\\Test7'" | Select-Object -Property Name, CurrentLogonUser,LastLogonUser
Name CurrentLogonUser LastLogonUser
---- ---------------- -------------
VM01-W10-22H2 DEMO\Test7 defaultuser0
Now, we have all the computer names.
Performing all the SCCM Client Actions on each computer (in version v10)
There are a couple of options:
- The computer cannot be contacted
- The computer is on, but nobody is logged on.
- The computer has AppLocker policies assigned to it.
- The CurrentLogonUser is logged on.
Ad 1: If the computer is switched off, you will get an error that the computer cannot be reached via WinRM. If the computer name does not exist, nothing is done.
Ad 2: Only a limited set of policies will be refreshed:
- Hardware Inventory
- Machine Policy Assignments Request
- Machine Policy Evaluation
- Policy Agent Validate Machine Policy / Assignment
- Application manager policy action
Ad 3: Also, only a limited set of policies will be refreshed
If the computer has AppLocker policies assigned to it, it is impossible to run a script in the user context. Almost with 100% certainty, the script will fail. So, the policies are refreshed under the system account.
If the following two registry keys are present, then AppLocker is effective.
HKLM:\SYSTEM\CurrentControlSet\Control\SRP\GP
HKLM:\SOFTWARE\Policies\Microsoft\Windows\SRPV2
Ad 4: The policies will be refreshed with a scheduled task that runs under the local user context
Creating a scheduled task is straightforward. But running it without a PowerShell window appearing is a bigger challenge.
One of the options I found was:
mshta vbscript:Execute("CreateObject(""Wscript.Shell"").Run ""powershell -NoLogo -Command """"& 'C:\Example Path That Has Spaces\My Script.ps1'"""""", 0 : window.close")
Source: How to run a PowerShell script without displaying a window? - Stack Overflow
What happens? I received an email from the security officer about what I was doing. It appears that mshta.exe raises all kinds of red flags, so that is a no-go.
Then the alternative: a vbscript that starts the PowerShell script. That works.
The following is done:
- A vbscript is created and stored under %ProgramData%\SCCMClientActions\PerformAllClientSCCMActions.vbs
On Error Resume Next
strCommand = "powershell -NoLogo -NoProfile -Command " + chr(34) + "& {$TMPFolder = $($Env:TEMP); Start-Transcript -Path ""$TMPFolder\AllSCCMClientActions.txt""; (New-Object -Comobject CPApplet.CPAppletMgr).GetClientActions() | ForEach-Object {Write-host ""Performing action: $($_.Name)"";$_.PerformAction()}; Stop-Transcript}" + chr(34)
set oWShell = CreateObject("WScript.Shell")
intReturn = oWShell.Run(strCommand, 0, true)
WScript.Quit intReturn
The scheduled task is created with the following details:
![]() |
![]() |
Program / Script: C:\WINDOWS\system32\wscript.exe
Add arguments: /e:vbscript "C:\ProgramData\SCCMClientActions\PerformAllClientSCCMActions.vbs"
While running the scheduled task, a transcript is created and placed in the users' temp folder.
**********************
Windows PowerShell transcript start
Start time: 20240525153414
Username: DEMO\test7
RunAs User: DEMO\test7
Configuration Name:
Machine: VM01-W10-22H2 (Microsoft Windows NT 10.0.19045.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -Command & {$TMPFolder = $($Env:TEMP); Start-Transcript -Path $TMPFolder\AllSCCMClientActions.txt; (New-Object -Comobject CPApplet.CPAppletMgr).GetClientActions() | ForEach-Object {Write-host Performing action: $($_.Name);$_.PerformAction()}; Stop-Transcript}
Process ID: 6520
PSVersion: 5.1.19041.4412
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.19041.4412
BuildVersion: 10.0.19041.4412
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
Transcript started, output file is C:\Users\test7\AppData\Local\Temp\AllSCCMClientActions.txt
Performing action: Software Metering Usage Report Cycle
Performing action: Request & Evaluate Machine Policy
Performing action: Updates Source Scan Cycle
Performing action: Request & Evaluate User Policy
Performing action: Software Inventory Collection Cycle
Performing action: Application Global Evaluation Task
Performing action: Software Updates Assignments Evaluation Cycle
Performing action: MSI Product Source Update Cycle
Performing action: Standard File Collection Cycle
**********************
Windows PowerShell transcript end
End time: 20240525153415
**********************
When the script has been run, that transcript file is read, and the information is stored in the array with the results.
The end result on the client
It might look like the example below:
User DEMO\Test7 on VM01-W10-22H2 is a regular user. So, all the actions are done.
User DEMO\Test6 on VM02-W10-22H2 logged on on a device with AppLocker enabled. As a result, only the default machine policies are applied.
On the VM03-W10-22H2, nobody is logged on. So, only the default machine policies are applied.
Performing all the SCCM Client Actions on each computer (in version v11 and later)
In version v11, a new method of triggering the per-user deployments is used. Now, I use root\ccm\Policy\machine\ActualConfig
for per-system deployments and root\ccm\Policy\<UsersSID>\ActualConfig
for the per-user deployments. The class CCM_Scheduler_ScheduledMessage is checked for all the ScheduledMessageID starting with {0000. If that is the case, the action is triggered if the ScheduledMessageID is in a predefined array of actions.
$WMIAction = ([wmi]"root\ccm\Policy\$PolicyPart\ActualConfig:CCM_Scheduler_ScheduledMessage.ScheduledMessageID='$($Action.ScheduledMessageID)'")
$WMIAction.Triggers=@('SimpleInterval;Minutes=1;MaxRandomDelayMinutes=0')
$WMIAction.Put()
The benefit is that there is no need to worry about bitlocker as the actions are performed under system context.
You can get the output of all the actions directly on screen or in a gridview.


And you see that in the PolicyAgentProvider:

Get-Help
Get-Help "AllSCCMClientActionsOnComputers_v11.ps1" -Detailed
shows the following information:
NAME
C:\temp\Perform all SCCM Client Actions on remote computers\v11\AllSCCMClientActionsOnComputers_v11.ps1
SYNOPSIS
Performs all the SCCM Client Actions on remote computers.
SYNTAX
C:\temp\Perform all SCCM Client Actions on remote computers\v11\AllSCCMClientActionsOnComputers_v11.ps1 [-SCCMServe
r <String>] [-SCCMSiteCode <String>] [-Groupname <String>] [-CollectionName <String>] [-ComputerNames <String[]>] [
-ShowSummary] [-ShowOnlyErrors] [-OnlyMachinePolicies] [-OnlyUserPolicies] [-DetailedLogging] [-ResetPolicy <String
>] [<CommonParameters>]
DESCRIPTION
This script performs all the SCCM Client actions on remote computers. You can specify a bunch of computers
or an AD group with users. The the device where the user is logged on will be targetted.
PARAMETERS
-SCCMServer <String>
-SCCMSiteCode <String>
-Groupname <String>
-CollectionName <String>
-ComputerNames <String[]>
-ShowSummary [<SwitchParameter>]
-ShowOnlyErrors [<SwitchParameter>]
-OnlyMachinePolicies [<SwitchParameter>]
-OnlyUserPolicies [<SwitchParameter>]
-DetailedLogging [<SwitchParameter>]
-ResetPolicy <String>
<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer, PipelineVariable, and OutVariable. For more information, see
about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216).
-------------------------- EXAMPLE 1 --------------------------
PS C:\>Performs the client sccm actions on the computers from the users in the given groupname.
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -Groupname SomeGroupName
-------------------------- EXAMPLE 2 --------------------------
PS C:\>Performs the client sccm actions on the computers from the users in the given groupname. (with spaces in its
name)
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -Groupname 'Some - GroupNa
meWithSpaces'
-------------------------- EXAMPLE 3 --------------------------
PS C:\>Performs the client sccm actions on the computers with the name pc-with-a-name and all the computer which name starts with vm-
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -ComputerNamesForClientAct
ions pc-with-a-name,vm-%
-------------------------- EXAMPLE 4 --------------------------
PS C:\>Performs the client sccm actions on the computers with are part of the collection 'User - Collection1'
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName 'User - Co
llection1'
-------------------------- EXAMPLE 5 --------------------------
PS C:\>Performs the client sccm actions on the computers with are part of the collection 'DeviceCollection2'
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName DeviceColl
ection2
-------------------------- EXAMPLE 6 --------------------------
PS C:\>Performs the client sccm actions on the computers with are part of the collection 'DeviceCollection2' and o
nly show the errors in the datagrid.
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName DeviceColl
ection2 -ShowOnlyErrors
-------------------------- EXAMPLE 7 --------------------------
PS C:\>Performs the client sccm actions on the computers with are part of the collection 'DeviceCollection2' and re
set the policy on the given computers
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName DeviceColl
ection2 -ResetPolicy purge
-------------------------- EXAMPLE 8 --------------------------
PS C:\>Performs only the machine policy actions the client sccm actions on the computer with the name vm01-w10-22h2
."AllSCCMClientActionsOnComputers_v11.ps1" -Computernames vm01-w10-22h2 -OnlyMachinePolicies
-------------------------- EXAMPLE 9 --------------------------
PS C:\>Performs only the user policy actions the client sccm actions on the computer with the name vm01-w10-22h2
."AllSCCMClientActionsOnComputers_v11.ps1" -Computernames vm01-w10-22h2 -OnlyUserPolicies
REMARKS
To see the examples, type: "get-help C:\temp\Perform all SCCM Client Actions on remote computers\v11\AllSCCMClientA
ctionsOnComputers_v11.ps1 -examples".
For more information, type: "get-help C:\temp\Perform all SCCM Client Actions on remote computers\v11\AllSCCMClient
ActionsOnComputers_v11.ps1 -detailed".
For technical information, type: "get-help C:\temp\Perform all SCCM Client Actions on remote computers\v11\AllSCCMC
lientActionsOnComputers_v11.ps1 -full".
The script:
<#
.SYNOPSIS
Performs all the SCCM Client Actions on remote computers.
.DESCRIPTION
This script performs all the SCCM Client actions on remote computers. You can specify a bunch of computers
or an AD group with users. The the device where the user is logged on will be targetted.
.EXAMPLE
Performs the client sccm actions on the computers from the users in the given groupname.
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -Groupname SomeGroupName
.EXAMPLE
Performs the client sccm actions on the computers from the users in the given groupname. (with spaces in its name)
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -Groupname 'Some - GroupNameWithSpaces'
.EXAMPLE
Performs the client sccm actions on the computers with the name pc-with-a-name and all the computer which name starts with vm-
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -ComputerNamesForClientActions pc-with-a-name,vm-%
.EXAMPLE
Performs the client sccm actions on the computers with are part of the collection 'User - Collection1'
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName 'User - Collection1'
.EXAMPLE
Performs the client sccm actions on the computers with are part of the collection 'DeviceCollection2'
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName DeviceCollection2
.EXAMPLE
Performs the client sccm actions on the computers with are part of the collection 'DeviceCollection2' and only show the errors in the datagrid.
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName DeviceCollection2 -ShowOnlyErrors
.EXAMPLE
Performs the client sccm actions on the computers with are part of the collection 'DeviceCollection2' and reset the policy on the given computers
."AllSCCMClientActionsOnComputers_v11.ps1" -SCCMServer sccm-servername -SCCMSiteCode tst -CollectionName DeviceCollection2 -ResetPolicy purge
.EXAMPLE
Performs only the machine policy actions the client sccm actions on the computer with the name vm01-w10-22h2
."AllSCCMClientActionsOnComputers_v11.ps1" -Computernames vm01-w10-22h2 -OnlyMachinePolicies
.EXAMPLE
Performs only the user policy actions the client sccm actions on the computer with the name vm01-w10-22h2
."AllSCCMClientActionsOnComputers_v11.ps1" -Computernames vm01-w10-22h2 -OnlyUserPolicies
.NOTES
Author: Willem-Jan Vroom
v0.1:
* Initial version
v0.2
* Improved layout
* Command line options.
v0.3:
* Improved layout
* Support for machines with AppLocker
* Also user policies are supported by using a scheduled task.
v1.0:
* Final version.
v1.1:
* Added parameter -ShowSummary to show the summary in the end.
* No longer the Scheduled action to do all the client actions.
* AppLocker restrictions have been removed.
* No use of scheduled tasks to run the user actions.
#>
[CmdletBinding(DefaultParameterSetName = 'Default')]
Param
(
[Parameter(HelpMessage='Servername SCCM / MECM Site server.')]
[Parameter(ParameterSetName = 'Default', Mandatory=$True)] [String] $SCCMServer,
[Parameter(HelpMessage='SCCM / MECM Site code.')]
[Parameter(ParameterSetName = 'Default', Mandatory=$True)] [String] $SCCMSiteCode,
[Parameter(HelpMessage ='Give the groupname (with single quotes if the groupname contains a space)')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [String] $Groupname,
[Parameter(HelpMessage ='Give the collectioname (with single quotes if the collectionname contains a space)')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [String] $CollectionName,
[Parameter(HelpMessage ='Enter the computername(s) to apply all the SCCM Client Actions on. Use a comma between each computername.')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [String[]] $ComputerNames,
[Parameter(HelpMessage ='Show the summary in the end.')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [Switch] $ShowSummary,
[Parameter(HelpMessage ='Show only the errors in the datagrid in the end.')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [Switch] $ShowOnlyErrors,
[Parameter(HelpMessage ='Perform only the machine policy actions.')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [Switch] $OnlyMachinePolicies,
[Parameter(HelpMessage ='Perform only the user policy actions.')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [Switch] $OnlyUserPolicies,
[Parameter(HelpMessage='Enable detailed logging to a log file.')]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [Switch] $DetailedLogging,
[Parameter(HelpMessage='Perform a policy reset.')]
[ValidateSet("none","full","purge")]
[Parameter(Mandatory = $False, ParameterSetName='Default')] [String] $ResetPolicy = "none"
)
# =============================================================================================================================================
# script block
# =============================================================================================================================================
$ScriptBlock = {
Param
(
[String] $User,
[String] $FullName,
[String] $ResetPolicy,
[String] $OnlyMachinePolicies,
[String] $OnlyUserPolicies,
[String] $UserSID
)
Function Convert-SSCMGuidToActionName
{
# Source: https://learn.microsoft.com/en-us/mem/configmgr/develop/reference/core/clients/client-classes/triggerschedule-method-in-class-sms_client
Param
(
[Parameter(HelpMessage='Enter the GUID.')]
[Parameter(Mandatory=$True, ParameterSetName='Optional')] [String] $GUIDForAction
)
Switch ($GUIDForAction)
{
"{00000000-0000-0000-0000-000000000001}" {$ScheduleName = "Hardware Inventory"}
"{00000000-0000-0000-0000-000000000002}" {$ScheduleName = "Software Inventory"}
"{00000000-0000-0000-0000-000000000003}" {$ScheduleName = "Data Discovery Record"}
"{00000000-0000-0000-0000-000000000010}" {$ScheduleName = "File Collection"}
"{00000000-0000-0000-0000-000000000011}" {$ScheduleName = "IDMIF Collection"}
"{00000000-0000-0000-0000-000000000012}" {$ScheduleName = "Client Machine Authentication"}
"{00000000-0000-0000-0000-000000000021}" {$ScheduleName = "Machine Policy Assignments Request"}
"{00000000-0000-0000-0000-000000000022}" {$ScheduleName = "Machine Policy Evaluation"}
"{00000000-0000-0000-0000-000000000023}" {$ScheduleName = "Refresh Default MP Task"}
"{00000000-0000-0000-0000-000000000024}" {$ScheduleName = "LS (Location Service) Refresh Locations Task"}
"{00000000-0000-0000-0000-000000000025}" {$ScheduleName = "LS (Location Service) Timeout Refresh Task"}
"{00000000-0000-0000-0000-000000000026}" {$ScheduleName = "Policy Agent Request Assignment (User)"}
"{00000000-0000-0000-0000-000000000027}" {$ScheduleName = "Policy Agent Evaluate Assignment (User)"}
"{00000000-0000-0000-0000-000000000031}" {$ScheduleName = "Software Metering Generating Usage Report"}
"{00000000-0000-0000-0000-000000000032}" {$ScheduleName = "Source Update Message"}
"{00000000-0000-0000-0000-000000000037}" {$ScheduleName = "Clearing proxy settings cache"}
"{00000000-0000-0000-0000-000000000040}" {$ScheduleName = "Machine Policy Agent Cleanup"}
"{00000000-0000-0000-0000-000000000041}" {$ScheduleName = "User Policy Agent Cleanup"}
"{00000000-0000-0000-0000-000000000042}" {$ScheduleName = "Policy Agent Validate Machine Policy / Assignment"}
"{00000000-0000-0000-0000-000000000043}" {$ScheduleName = "Policy Agent Validate User Policy / Assignment"}
"{00000000-0000-0000-0000-000000000051}" {$ScheduleName = "Retrying/Refreshing certificates in AD on MP"}
"{00000000-0000-0000-0000-000000000061}" {$ScheduleName = "Peer DP Status reporting"}
"{00000000-0000-0000-0000-000000000062}" {$ScheduleName = "Peer DP Pending package check schedule"}
"{00000000-0000-0000-0000-000000000063}" {$ScheduleName = "SUM Updates install schedule"}
"{00000000-0000-0000-0000-000000000101}" {$ScheduleName = "Hardware Inventory Collection Cycle"}
"{00000000-0000-0000-0000-000000000102}" {$ScheduleName = "Software Inventory Collection Cycle"}
"{00000000-0000-0000-0000-000000000103}" {$ScheduleName = "Discovery Data Collection Cycle"}
"{00000000-0000-0000-0000-000000000104}" {$ScheduleName = "File Collection Cycle"}
"{00000000-0000-0000-0000-000000000105}" {$ScheduleName = "IDMIF Collection Cycle"}
"{00000000-0000-0000-0000-000000000106}" {$ScheduleName = "Software Metering Usage Report Cycle"}
"{00000000-0000-0000-0000-000000000107}" {$ScheduleName = "Windows Installer Source List Update Cycle"}
"{00000000-0000-0000-0000-000000000108}" {$ScheduleName = "Software Updates Assignments Evaluation Cycle"}
"{00000000-0000-0000-0000-000000000109}" {$ScheduleName = "Branch Distribution Point Maintenance Task"}
"{00000000-0000-0000-0000-000000000111}" {$ScheduleName = "Send Unsent State Message"}
"{00000000-0000-0000-0000-000000000112}" {$ScheduleName = "State System policy cache cleanout"}
"{00000000-0000-0000-0000-000000000113}" {$ScheduleName = "Scan by Update Source"}
"{00000000-0000-0000-0000-000000000114}" {$ScheduleName = "Update Store Policy"}
"{00000000-0000-0000-0000-000000000115}" {$ScheduleName = "State system policy bulk send high"}
"{00000000-0000-0000-0000-000000000116}" {$ScheduleName = "State system policy bulk send low"}
"{00000000-0000-0000-0000-000000000121}" {$ScheduleName = "Application manager policy action"}
"{00000000-0000-0000-0000-000000000122}" {$ScheduleName = "Application manager user policy action"}
"{00000000-0000-0000-0000-000000000123}" {$ScheduleName = "Application manager global evaluation action"}
"{00000000-0000-0000-0000-000000000131}" {$ScheduleName = "Power management start summarizer"}
"{00000000-0000-0000-0000-000000000221}" {$ScheduleName = "Endpoint deployment reevaluate"}
"{00000000-0000-0000-0000-000000000222}" {$ScheduleName = "Endpoint AM policy reevaluate"}
"{00000000-0000-0000-0000-000000000223}" {$ScheduleName = "External event detection"}
default {$ScheduleName = "Ohoh.... something went wrong here..."}
}
Return $ScheduleName
}
$PolicyResetDone = "-"
$ArrayWithActions = @()
$WMIParam = @{"Class" = "SMS_Client"
"Namespace" = "root\ccm"}
If ($ResetPolicy -ne "none")
{
$ActionName = "-- Performing reset policy. --"
If ($ResetPolicy -eq "full") {$uFlag = 0} else {$uFlag = 1}
Try
{
(Get-WmiObject @WMIParam -List).ResetPolicy($uFlag)
$Result = "Ok"
}
Catch
{
$Result = "Error"
$ErrorMessage = "[$($_.Exception.Message)] Type [$($_.Exception.GetType().FullName)]"
}
Finally
{
$Record = [Ordered]@{"User logon name"= $User
"Fullname" = $FullName
"Computer" = $env:COMPUTERNAME
"Remark" = "Policy reset [$ResetPolicy] -> [$Result]"
"Error" = $ErrorMessage}
$ArrayWithActions += New-Object -TypeName PSObject -Property $Record
}
}
$ErrorMessage = ""
$ConditionNoUser = (-not $User)
$ConditionOnlyMachinePolicies = $OnlyMachinePolicies -eq "yes"
$ConditionOnlyUserPolicies = $OnlyUserPolicies -eq "yes"
$Result = ""
[String[]]$RemarkText = ""
$ClientActions = @("{00000000-0000-0000-0000-000000000001}",
"{00000000-0000-0000-0000-000000000002}",
"{00000000-0000-0000-0000-000000000010}",
"{00000000-0000-0000-0000-000000000021}",
"{00000000-0000-0000-0000-000000000022}",
"{00000000-0000-0000-0000-000000000024}",
"{00000000-0000-0000-0000-000000000025}",
"{00000000-0000-0000-0000-000000000026}",
"{00000000-0000-0000-0000-000000000027}",
"{00000000-0000-0000-0000-000000000032}",
"{00000000-0000-0000-0000-000000000040}",
"{00000000-0000-0000-0000-000000000041}",
"{00000000-0000-0000-0000-000000000042}",
"{00000000-0000-0000-0000-000000000043}",
"{00000000-0000-0000-0000-000000000111}",
"{00000000-0000-0000-0000-000000000112}",
"{00000000-0000-0000-0000-000000000113}",
"{00000000-0000-0000-0000-000000000114}",
"{00000000-0000-0000-0000-000000000121}",
"{00000000-0000-0000-0000-000000000122}")
If ($ConditionNoUser)
{$RemarkText += "No user logged on."}
If (($ConditionOnlyMachinePolicies -or $ConditionNoUser) -and (-not $ConditionOnlyUserPolicies))
{$RemarkText += "Only the default machine actions are triggered."}
If ($ConditionOnlyUserPolicies)
{$RemarkText += "Only the user policy actions are triggered."}
$PolicyParts = @()
If (-not $ConditionOnlyUserPolicies)
{$PolicyParts += "Machine"}
If ((-not $ConditionOnlyMachinePolicies) -and ($UserSID -ne "-"))
{$UserSID = $UserSID.Replace("-","_")
$PolicyParts +=$UserSID}
If (($PolicyParts | Measure-Object).Count -eq 0)
{
$Record = [Ordered]@{"User logon name"= $User
"Fullname" = $FullName
"Computer" = $env:COMPUTERNAME
"Actionname" = ""
"Scope" = $Scope
"Remark" = ($RemarkText -Join " ").Trim()
"Error" = $ErrorMessage}
$ArrayWithActions += New-Object -TypeName PSObject -Property $Record
}
else
{
ForEach ($PolicyPart in $PolicyParts)
{
$Namespace = "root\ccm\Policy\$PolicyPart\ActualConfig"
If ($PolicyPart -eq "Machine") {$Scope = $PolicyPart} else {$Scope = "User"}
$Actions = Get-CimInstance -ClassName CCM_Scheduler_ScheduledMessage -Namespace $Namespace -Filter "ScheduledMessageID like '{0000%'" | Select-Object -Property ScheduledMessageID
ForEach ($Action in $Actions)
{
$ErrorMessage = $Null
If ($($Action.ScheduledMessageID) -in $ClientActions)
{
Try
{
$WMIAction = ([wmi]"root\ccm\Policy\$PolicyPart\ActualConfig:CCM_Scheduler_ScheduledMessage.ScheduledMessageID='$($Action.ScheduledMessageID)'")
$WMIAction.Triggers=@('SimpleInterval;Minutes=1;MaxRandomDelayMinutes=0')
$WMIAction.Put()
}
Catch
{
$ErrorMessage = "[$($_.Exception.Message)] Type [$($_.Exception.GetType().FullName)]"
}
Finally
{
$Record = [Ordered]@{"User logon name" = $User
"Fullname" = $FullName
"Computer" = $env:COMPUTERNAME
"Actionname" = Convert-SSCMGuidToActionName -GUIDForAction $($Action.ScheduledMessageID)
"Scope" = $Scope
"Remark" = ($RemarkText -Join " ").Trim()
"Error" = $ErrorMessage}
$ArrayWithActions += New-Object -TypeName PSObject -Property $Record
}
}
}
}
}
Return $ArrayWithActions
}
# =============================================================================================================================================
# End script block
# =============================================================================================================================================
# =============================================================================================================================================
# Function block
# =============================================================================================================================================
Function Add-EntryToLogFile
{
<#
.NOTES
=============================================================================================================================================
Created with: Windows PowerShell ISE
Created on: 17-May-2020 / Modified 09-May-2022: Includes the function name
Created by: Willem-Jan Vroom
Organization:
Functionname: Add-EntryToLogFile
=============================================================================================================================================
.SYNOPSIS
This function adds a line to a log file
#>
Param
(
[Parameter(Mandatory=$True)] [string] $Entry,
[Parameter(Mandatory=$False)] [String] $FunctionName
)
Write-Verbose "[Function: $FunctionName] - $Entry"
If ($Global:gblDetailedLogging -and $Global:gblLogFile)
{
$Timestamp = (Get-Date -format "yyyy-MM-dd HH-mm-ss").ToString()
Add-Content $Global:gblLogFile -Value $($Timestamp + "[Function: $FunctionName] - $Entry") -Force -ErrorAction SilentlyContinue
}
}
Function Add-RecordToLogFile
{
<#
.NOTES
=============================================================================================================================================
Created with: Windows PowerShell ISE
Created on: 17-May-2020 / Modified 09-May-2022: Includes the function name
Created by: Willem-Jan Vroom
Organization:
Functionname: Add-RecordToLogFile
=============================================================================================================================================
.SYNOPSIS
This function adds a record to the log file
#>
Param
(
[Parameter(Mandatory=$True)] $Record
)
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry ">>> The variable 'Record' has as type: $($Record.GetType().Name)."
If ($($Record.GetType().Name) -eq "Object[]" -or $($Record.GetType().Name) -eq "CimInstance")
{$Headers = ([array]$Record | Get-Member -MemberType Properties).Name}
else
{$Headers = @([PSCustomObject]$Record.psobject.properties |Where-Object {$_.Name -eq 'Keys'}).Value}
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The following information is gathered:"
ForEach ($Item in $Record)
{
ForEach ($Header in $Headers)
{
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "$Header --> $($Item.$Header)"
}
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "- - - - - - - - - - - - - - - - - - - - - - - - - - -"
}
}
Function Get-ADUserName
{
<#
.NOTES
=============================================================================================================================================
Created with: Windows PowerShell ISE
Created on: 27-April 2024
Created by: Willem-Jan Vroom
Organization:
Functionname: Get-ADUserName
=============================================================================================================================================
.SYNOPSIS
This function converts the 'domain\user' to the users' name.
#>
Param
(
[Parameter(Mandatory=$True)][String] $UsernameInDomainUserFormat
)
$ReturnValue = $Null
If (($UsernameInDomainUserFormat).Contains("\"))
{
$Domain = ($UsernameInDomainUserFormat.Split("\"))[0]
$User = ($UsernameInDomainUserFormat.Split("\"))[1]
$ReturnValue = ([adsi]"WinNT://$Domain/$User,user").fullname
}
else
{
$ReturnValue = $UsernameInDomainUserFormat
}
Return $ReturnValue
}
Function Get-ADUserSID
{
<#
.NOTES
=============================================================================================================================================
Created with: Windows PowerShell ISE
Created on: 27-April 2024
Created by: Willem-Jan Vroom
Organization:
Functionname: Get-ADUserSID
=============================================================================================================================================
.SYNOPSIS
This function converts the 'domain\user' to the users' SID.
#>
Param
(
[Parameter(Mandatory=$True)][String] $UsernameInDomainUserFormat
)
$ReturnValue = $Null
If (($UsernameInDomainUserFormat).Contains("\"))
{
$Domain = ($UsernameInDomainUserFormat.Split("\"))[0]
$User = ($UsernameInDomainUserFormat.Split("\"))[1]
$strSID = (New-Object System.Security.Principal.NTAccount($Domain, $User)).Translate([System.Security.Principal.SecurityIdentifier])
$ReturnValue = $strSID.Value
}
else
{
$ReturnValue = $UsernameInDomainUserFormat
}
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "The user [$UsernameInDomainUserFormat] has SID [$ReturnValue]."
Return $ReturnValue
}
Function Run-ClientSCCMActionsOnRemoteComputers
{
<#
.NOTES
=============================================================================================================================================
Created with: Windows PowerShell ISE
Created on: 27-April 2024
Created by: Willem-Jan Vroom
Organization:
Functionname: Run-ClientSCCMActionsOnRemoteComputers
=============================================================================================================================================
.SYNOPSIS
This function starts the SCCM on the remote computers.
#>
Param
(
[Parameter(Mandatory=$True)][String] $WMIClass,
[Parameter(Mandatory=$True)][String] $WMIFilter,
[Parameter(Mandatory=$True)] $WMIParam
)
$ArrayWithTheResults = @()
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Running query: [Select * From $WMIClass Where $WMIFilter]"
$Computers = @(Get-CimInstance @WMIParam -Class $WMIClass -Filter $WMIFilter | Select-Object -Property Name,CurrentLogonUser) | Sort-Object -Property Name
ForEach ($Computer in $Computers)
{
If($($Computer.CurrentLogonUser))
{$User = "$($Computer.CurrentLogonUser)";$FullName = Get-ADUserName -UsernameInDomainUserFormat $($Computer.CurrentLogonUser)}
else
{$User = $Null;$FullName = "-"}
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Discovered userid on $($Computer.Name) : $User"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Discovered fullname on $($Computer.Name) : $FullName"
Try
{
$Result = $Null
$ResetPolicy = $Global:ResetPolicy
If ($($Global:OnlyMachinePolicies.IsPresent)) {$OnlyMachinePolicies = "yes"} else {$OnlyMachinePolicies = $Null}
If ($($Global:OnlyUserPolicies.IsPresent)) {$OnlyUserPolicies = "yes"} else {$OnlyUserPolicies = $null}
If ($User)
{$UserSID = Get-ADUserSID -UsernameInDomainUserFormat $User}
else
{$UserSID = "-"}
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Performing the actions on the computer [$($Computer.Name)]."
$Result = Invoke-Command -ComputerName $Computer.Name -ScriptBlock $ScriptBlock -ArgumentList $User,$FullName,$ResetPolicy,$OnlyMachinePolicies,$OnlyUserPolicies,$UserSID -ErrorAction Stop
}
Catch [System.Management.Automation.Remoting.PSRemotingTransportException]
{
$ErrorMessage = "The host [$($Computer.Name)] cannot be reached via PSRemoting (WinRM)"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Error [$ErrorMessage]"
}
Catch
{
$ErrorMessage = "Host [$($Computer.Name)]: [$($_.Exception.Message)] Type [$($_.Exception.GetType().FullName)]"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Error [$ErrorMessage]"
}
Finally
{
If (-not $Result)
{
$Record = [Ordered]@{"Userdetails"= $User
"Fullname" = $FullName
"Computer" = $Computer.Name
"Actionname" = ""
"Error" = $ErrorMessage}
$Result = New-Object -TypeName PSObject -Property $Record
}
$ArrayWithTheResults += $Result
Add-RecordToLogFile -Record $Result
}
}
Return $ArrayWithTheResults
}
# =============================================================================================================================================
# End function block
# =============================================================================================================================================
$WMIParam = @{ComputerName = $SCCMServer
NameSpace = "root\sms\site_$SCCMSiteCode"}
$strCurrentDir = Split-Path -parent $MyInvocation.MyCommand.Definition
$Global:gblLogPath = $strCurrentDir
$Global:gblDetailedLogging = $DetailedLogging
$DateTime = (Get-Date -format "yyyy-MM-dd_HH-mm-ss").ToString()
$Global:gblLogFile = "$($Global:gblLogPath)\$($Env:USERNAME)_$($DateTime)_AllSCCMClientActionsOnRemoteComputers.log"
$Global:ResetPolicy = $ResetPolicy
$Global:OnlyMachinePolicies = $OnlyMachinePolicies
$Global:OnlyUserPolicies = $OnlyUserPolicies
If ($Global:gblDetailedLogging)
{
New-Item $Global:gblLogFile -ItemType File -Force | Out-Null
}
Clear-Host
If (-not $Groupname -and -not $ComputerNames -and -not $CollectionName)
{
$ErrorMessage = "One of the parameters Groupname, CollectionName or ComputerNameForClientActions should be mentioned."
Write-Warning $ErrorMessage
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry $ErrorMessage
Exit 999
}
$ArrayWithAllTheActionResults = @()
If ($CollectionName)
{
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Collection details are mentioned..."
$Query = "SELECT CollectionType,CollectionID,Name,MemberClassName From SMS_Collection Where Name = '$CollectionName'"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Running query: [$Query]"
$CollectionDetails = Get-CimInstance @WMIParam -Query $Query| Select-Object -Property CollectionType,CollectionID,Name,MemberClassName
If ($CollectionDetails.CollectionType -eq 1) # User collection
{
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "User collection...."
$WMIClass = $CollectionDetails.MemberClassName
$Query = "SELECT SMSID From $WMIClass"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Running query: [$Query]"
$UserOrGroupName = @(Get-CimInstance @WMIParam -Query $Query | Select-Object -Property SMSID)
If ($UserOrGroupName.Count -eq 1)
{
$UserDomain = $($UserOrGroupName.SMSID).Split("\")[0]
$UserName = $($UserOrGroupName.SMSID).Split("\")[1]
}
else
{
$UserDomain = $($UserOrGroupName.SMSID[-1]).Split("\")[0]
$UserName = $($UserOrGroupName.SMSID[-1]).Split("\")[1]
}
If (([adsi]"WinNT://$UserDomain/$UserName").Properties.GroupType) # Check if it is a group or user.
{
$Groupname = $UserOrGroupName.SMSID
}
else
{
$WMIClass = "SMS_CombinedDeviceResources"
[int32]$Counter = 1
[int32]$TotalItems = $UserOrGroupName.Count
ForEach ($Item in $UserOrGroupName)
{
Write-Progress -Id 1 -Activity "Going through the users of the collection [$CollectionName]." -Status "Processing user [$($Item.SMSID) - $(Get-ADUserName -UsernameInDomainUserFormat $($Item.SMSID))]" -PercentComplete ($Counter / $TotalItems * 100)
$Counter ++
$WMIFilter = "CurrentLogonUser = '$($($Item.SMSID).Replace('\','\\'))'"
$ArrayWithAllTheActionResults += @(Run-ClientSCCMActionsOnRemoteComputers -WMIClass $WMIClass -WMIFilter $WMIFilter -WMIParam $WMIParam)
}
}
}
ElseIf ($CollectionDetails.CollectionType -eq 2) # DeviceCollection
{
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Device collection...."
$WMIClass = $CollectionDetails.MemberClassName
$Query = "SELECT Name From $WMIClass"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Running query: [$Query]"
$ComputerNames = @(Get-CimInstance @WMIParam -Query $Query | Select-Object -Property Name).Name
}
}
If ($Groupname)
{
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Running the query....."
$GroupName = $GroupName.Replace("\","\\")
$Query = "select SMS_R_User.UniqueUserName from SMS_R_User where SMS_R_User.UserGroupName LIKE '%$GroupName'"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry "Running query: [$Query]"
$UniqueUserNames = @(Get-WMIObject @WMIParam -Query $Query | Select-Object -Property UniqueUserName)
[Int32] $UniqueUserNamesCount = $UniqueUserNames.Count
[Int32] $Counter = 1
$WMIClass = "SMS_CombinedDeviceResources"
ForEach ($UniqueUserName in $UniqueUserNames)
{
Write-Progress -Id 1 -Activity "Going through the users of group [$Groupname]." -Status "Processing user [$($UniqueUserName.UniqueUserName) - $(Get-ADUserName -UsernameInDomainUserFormat $($UniqueUserName.UniqueUserName))]" -PercentComplete ($Counter / $UniqueUserNamesCount * 100)
$Counter++
$WMIFilter = "CurrentLogonUser = '$($($UniqueUserName.UniqueUserName).Replace('\','\\'))'"
$ArrayWithAllTheActionResults += @(Run-ClientSCCMActionsOnRemoteComputers -WMIClass $WMIClass -WMIFilter $WMIFilter -WMIParam $WMIParam)
}
}
else
{
[int32]$Counter = 1
[int32]$TotalItems = $ComputerNames.Count
ForEach ($ComputerNameForClientActions in $ComputerNames)
{
Write-Progress -Id 1 -Activity "Going through the computers." -Status "Processing computers that match [$ComputerNameForClientActions]" -PercentComplete ($Counter / $TotalItems * 100)
$Counter++
If ($ComputerNameForClientActions.Contains('%')) {$Operator = 'like'} else {$Operator = '='}
$WMIClass = "SMS_CombinedDeviceResources"
$WMIFilter = "Name $Operator '$ComputerNameForClientActions'"
$ArrayWithAllTheActionResults += @(Run-ClientSCCMActionsOnRemoteComputers -WMIClass $WMIClass -WMIFilter $WMIFilter -WMIParam $WMIParam)
}
}
$ArrayWithAllTheActionResults = $ArrayWithAllTheActionResults | Where-Object {$_.Computer -ne $Null} | Select-Object -Property "User logon name",FullName,Computer,Actionname,Scope,Remark,Error | Sort-Object -Property Computer,Actionname
If (($ArrayWithAllTheActionResults | Measure-Object).Count -gt 0)
{
$Title = "Results of performing SCCM Client Actions on computers."
If ($ShowOnlyErrors)
{
If ($ShowSummary.IsPresent)
{
$ArrayWithAllTheActionResults | Where-Object {$_.Error} | Sort-Object -Property Computer,Scope,ActionName | Out-GridView -Title "Results of performing SCCM Client Actions on computers."
}
else
{
$ArrayWithAllTheActionResults | Where-Object {$_.Error} | Sort-Object -Property Computer,Scope,ActionName | Format-Table -GroupBy Computer -Property FullName,ActionName,Scope,Remark,Error
}
}
else
{
If ($ShowSummary.IsPresent)
{
$ArrayWithAllTheActionResults | Sort-Object -Property Computer,Scope,ActionName | Out-GridView -Title "Results of performing SCCM Client Actions on computers."
}
else
{
$ArrayWithAllTheActionResults | Sort-Object -Property Computer,Scope,ActionName | Format-Table -GroupBy Computer -Property FullName,ActionName,Scope,Remark,Error
}
}
}
else
{
$ErrorMessage = "There is nothing to report......"
Add-EntryToLogFile -FunctionName $($MyInvocation.MyCommand.Name) -Entry $ErrorMessage
Write-Host $ErrorMessage
}
After extracting the ZIP file, please 'unblock' the Powershell script and/or the batch file.
