At Microsoft Ignite 2019 Brad Anderson demoed a few things in his sessions, later covered more in depth in other sessions, that highlighted some new features to allow you to leverage the cloud console to manage ConfigMgr-only devices. As I watched these sessions, I realized that they were using the ConfigMgr Administration Service to do integrate into the cloud.
Paul Mayfield showed how to sync ConfigMgr client policies on ConfigMgr-only, non co-managed, non Intune registered device (BRK2082 11:50 timestamp)
Brad Anderson shows a real-time application install on a ConfigMgr-only, non co-managed, non Intune registered device (BRK008 11:00 - timestamp)
Both of these demos use the ConfigMgr AdminService on the backend. I know I’ve been trying to sell the AdminService as a replacement for WMI, but it’s so much more. The AdminService will leverage your Cloud Management Gateway as a web proxy and allow you to take actions on Any Device from Anywhere (within reason). With Co-Managed and Intune-only devices, you already had much of this functionality; now with the Microsoft Endpoint Admin Center, you can leverage a centralized cloud console to manage devices, regardless of how they are managed.
In this post, I’d like to highlight some of the new features that have been enabled in in the AdminService in Microsoft Endpoint Configuration Manager 1910 Current Branch. *If you were at MMSJazz, you may have heard me talk about this in the 1911 Technical Preview.
WMI Methods!
Up until now, the AdminService has been largely read-only - you could only read WMI classes, but none of the WMI methods were available until now. In 1910, the WMI methods are all here! You can see them by listing the WMI route metadata and looking for ActionImport in the XML output.
https://<SERVERNAME>/AdminService/wmi/$metadata
If you visit the reference for the WMI class in question, you will be able to find the methods and method syntax. From there, you should be able to work out how to execute any WMI method.
SMS_ClientOperation
The Inside the ConfigMgr console, the right-click menu has several options which leverage the BGB (Big Green Button) or Fast Channel to communicate with the client. These include:
Run Script
Install Application
Client Notification
Download Computer Policy
Download User Policy
Collect Discovery Data
Collect Software Inventory
Collect Hardware Inventory
Evaluate Application Deployments
Evaluate Software Update Deployments
Switch To Next Software Update Point
Evaluate Device Health Attestation
Check Conditional Access Compliance
Wake Up
Restart
Client Diagnostics
Enable Verbose Logging
Disable Verbose Logging
Endpoint Protection
Full Scan
Quick Scan
Download Definition
If you watch SMSProv.log when you click these, you will see that they each call methods on the SMS_ClientOperation WMI class on the SMS Provider server.
Each device with the ConfigMgr client installed has a WMI class called ROOT\ccm\ClientSDK. Generally, whenever you want to do things on the client, you would need to remotely connect to the device, attach to the ClientSDK class and trigger a WMI method on the client. Essentially, everything is happening client-side. This is no good when you want to take actions from something like a web API or web console. When you trigger these fast channel methods, all of the logic is in the WMI method on the server side, not the client side. I’m sure I’m doing a terrible job of describing what actually happens here…
If we look up the class in the WMI reference https://docs.microsoft.com/configmgr/develop/reference/protect/sms_clientoperation-server-wmi-class we can see that there are several methods available to take actions on clients from the server. Here’s the PowerShell to trigger a reboot using the SMS_ClientOperation class using Type 17:
Now that we’ve got a handle on BGB and the SMS_ClientOperation class, let’s see how that translates over to the AdminService.
Triggering Client Operations with AdminService
The big win here is that the client Fast Channel will allow the AdminService to trigger client actions FROM ANYWHERE. Up until now, the AdminService was limited to server-side only methods and had none of the existing WMI methods. With ConfigMgr 1910 Current Branch, we will be able to start triggering client actions from any platform that can call a web API.
Using the same URL listed earlier https://<SERVERNAME>/AdminService/wmi/$metadata to list the metadata for the WMI route, if we search for SMS_ClientOperation, we will find all of the WMI Methods from the docs listed as ActionImport lines in XML.
Here’s a listing of the actions available for the SMS_ClientOperation WMI class in the AdminService metadata.
Now that we have the class and methods, we just need to trigger the WMI method using an OData Action. For OData, we will be using a HTTP POST method and sending a json formatted BODY as parameters to the method. The URL syntax for OData Actions used by AdminService is:
For this example it would be: https://<ServerName>/AdminService/wmi/SMS_ClientOperation.InitiateClientOperation
Since this is a POST method, you have to upload a body. As far as I know, you can’t do this natively in a browser and need to use a separate tool. I’m using PowerShell, but you can also use Telerik Fiddler or various other tools to do the same thing.
Here’s a sample bit of PowerShell that I put together to test out the Client Notification actions.
Param (
[Parameter(Mandatory=$true,HelpMessage="Enter your server name where AdminService is running (SMS Provider Role")]
[string]$ServerName,
[Parameter(Mandatory=$true,HelpMessage="Enter the ResourceID of the target device")]
[uint32[]]$TargetResourceIDs,
[Parameter(Mandatory=$false,HelpMessage="Enter a Collection ID that the target device is in")]
[string]$TargetCollectionID = "SMS00001")
$Types = [Ordered]@{
"DownloadComputerPolicy" = 8"DownloadUserPolicy" = 9"CollectDiscoveryData" = 10"CollectSoftwareInventory" = 11"CollectHardwareInventory" = 12"EvaluateApplicationDeployments" = 13"EvaluateSoftwareUpdateDeployments" = 14"SwitchToNextSoftwareUpdatePoint" = 15"EvaluateDeviceHealthAttestation" = 16"CheckConditionalAccessCompliance" = 125"WakeUp" = 150"Restart" = 17"EnableVerboseLogging" = 20"DisableVerboseLogging" = 21}
[uint32]$RandomizationWindow = 1[string]$MethodClass = "SMS_ClientOperation"[string]$MethodName = "InitiateClientOperation"[string]$ResultClass = "SMS_ClientOperationStatus"$Types.Keys | ForEach-Object {Write-Host$Types[$_] :$_}
[uint32]$Type = Read-Host -Prompt "Which client action?"$PostURL = "https://{0}/AdminService/wmi/{1}.{2}"-f$ServerName,$MethodClass,$MethodName$Headers = @{
"Content-Type" = "Application/json"}
$Body = @{
TargetCollectionID = $TargetCollectionIDType = $Type RandomizationWindow = $RandomizationWindow TargetResourceIDs = $TargetResourceIDs} | ConvertTo-JsonInvoke-RestMethod -Method Post -Uri "$($PostURL)" -Body $Body -Headers $Headers -UseDefaultCredentials | Select-Object ReturnValue
#Get Results$GetURL = "https://{0}/AdminService/wmi/{1}"-f$ServerName,$ResultClass(Invoke-RestMethod -Method Get -Uri "$($GetURL)" -UseDefaultCredentials).Value | Format-Table
The script will prompt for your server name and the ResourceID of the device you want to target (you can actually provide multiple ResourceIDs). The last 3 lines will query SMS_ClientOperationStatus and you will see a new entry for the ResourceID(s) that you targeted. This is all done using the newly added WMI methods in the AdminService.
To verify that the action is actually working, you can check the AdminService.Log and BGBServer.log on the SMS Provider server or the client log that corresponds to the action that you triggered.
The best part about WMI methods being available in AdminService now is that we can call them from anywhere. No need to use PowerShell remoting to connect to the server to call the WMI method. Any authorized user can call these methods. You can even use them in Task Sequences.
As if this wasn’t enough, the next part is even better. You may have seen my previous post where I showed how to trigger CMPivot through the Cloud Management Gateway - now you can do real-time client actions over the CMG. Sync client policies, Restart, or even Install Applications in real-time.
Triggering Client Operations Over the Internet
Next I’m going to step up the game and I’m going to trigger client actions on a Co-Managed device that isn’t on the company network from a non-company device on the internet. This will simulate being able to take actions on clients through the Microsoft Endpoint Manager Admin Center.
I’ve taken the same machine that I was testing on and moved it to a non-business network with internet only. Then I run a script to use the internet-facing URL of the AdminService on a personal device and I’m able to trigger a device restart on the internet-only device. It’s not even registered in Intune.
Param (
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Native App) - Overview. Copy the Application (client) ID")]
[string]
$ClientID,
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Native App) - Overview. Copy the Directory (tenant) ID")]
[string]
$TenantID,
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Cloud Management App) - Authentication. Copy the Redirect URI. It should start with ms-appx-web://")]
[string]
$RedirectURI,
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Cloud Management App) - Expose an API. Copy the Application ID URI")]
[string]
$ResourceAppIdURI,
[Parameter(Mandatory=$True, HelpMessage = "Query your SCCM DB - SELECT ExternalEndpointName, ExternalUrl FROM vProxy_Routings WHERE ExternalEndpointName = 'AdminService'")]
[string]
$InternetBaseURL,
[Parameter(Mandatory=$True, HelpMessage = "The URL to your AdminService server https://<FQDN>/AdminService")]
[string]
$InternalBaseURL,
[Parameter(Mandatory=$True, HelpMessage = "Use Token Auth or Current User Auth")]
[switch]
$UseTokenAuth = $True,
[Parameter(Mandatory=$True)]
[uint64[]]$TargetResourceIDs,
[Parameter(Mandatory=$True)]
[string]$TargetCollectionID)
$Main = {
[Switch]$TryTokenAuth = $false
If(!($UseTokenAuth.IsPresent)) {
Try {
$Data = Initiate-ClientAction -URL $InternetBaseURL -TargetResourceIDs $TargetResourceIDs -TargetCollectionID $TargetCollectionID }
Catch {
Write-Host"An error occurred using default credentials. Trying with Token Auth."$TryTokenAuth = $True
}
}
If($UseTokenAuth.IsPresent -or$TryTokenAuth) {
$AuthToken = Get-AADAuthToken -ClientID $ClientID -TenantID $TenantID -ResourceAppIDURI $ResourceAppIDURI -RedirectUri $RedirectUri -PromptForNewCredentials Auto
# Creating header for Authorization tokenIf($AuthToken) {
$AuthHeader = @{
'Content-Type' = 'application/json''Authorization' = "Bearer " + $AuthToken.AccessToken
'ExpiresOn' = $AuthToken.ExpiresOn
}
}
Else {
Write-Host"No Auth Token Found. Exiting."Break;
}
$Data = Initiate-ClientAction -URL $InternetBaseURL -TargetResourceIDs $TargetResourceIDs -TargetCollectionID $TargetCollectionID -authHeader $authHeader }
Return$Data.value
}
FunctionInitiate-ClientAction {
Param (
[Parameter(Mandatory=$true,HelpMessage="Enter your server name where AdminService is running (SMS Provider Role")]
[string]$URL,
[Parameter(Mandatory=$true,HelpMessage="Enter the ResourceID of the target device")]
[uint32[]]$TargetResourceIDs,
[Parameter(Mandatory=$false,HelpMessage="Enter a Collection ID that the target device is in")]
[string]$TargetCollectionID,
$authHeader )
$Types = [Ordered]@{
"DownloadComputerPolicy" = 8"DownloadUserPolicy" = 9"CollectDiscoveryData" = 10"CollectSoftwareInventory" = 11"CollectHardwareInventory" = 12"EvaluateApplicationDeployments" = 13"EvaluateSoftwareUpdateDeployments" = 14"SwitchToNextSoftwareUpdatePoint" = 15"EvaluateDeviceHealthAttestation" = 16"CheckConditionalAccessCompliance" = 125"WakeUp" = 150"Restart" = 17"EnableVerboseLogging" = 20"DisableVerboseLogging" = 21 }
[uint32]$RandomizationWindow = 1 [string]$MethodClass = "SMS_ClientOperation" [string]$MethodName = "InitiateClientOperation" [string]$ResultClass = "SMS_ClientOperationStatus"$Types.Keys | ForEach-Object {Write-Host$Types[$_] :$_}
[uint32]$Type = Read-Host -Prompt "Which client action?"$PostURL = "{0}/wmi/{1}.{2}"-f$URL,$MethodClass,$MethodName$Headers = @{
"Content-Type" = "Application/json" }
$Body = @{
TargetCollectionID = $TargetCollectionIDType = $Type RandomizationWindow = $RandomizationWindow TargetResourceIDs = $TargetResourceIDs } | ConvertTo-JsonIf($authHeader) {
Invoke-RestMethod -Method Post -Uri "$($PostURL)" -Body $Body -Headers $authHeader | Select-Object ReturnValue
}
Else {
Invoke-RestMethod -Method Post -Uri "$($PostURL)" -Body $Body -Headers $Headers -UseDefaultCredentials | Select-Object ReturnValue
}
#Get Results$GetURL = "{0}/wmi/{1}"-f$URL,$ResultClassIf($authHeader) {
$Result = (Invoke-RestMethod -Method Get -Uri "$($GetURL)" -Headers $authHeader).Value | Format-Table }
Else {
(Invoke-RestMethod -Method Get -Uri "$($GetURL)" -Headers $Headers -UseDefaultCredentials).Value | Format-Table }
Return$Result}
#Modified Script from Sandy's blog post#https://www.scconfigmgr.com/2019/07/16/use-configmgr-administration-service-adminservice-over-internet/#Follow the instructions in her blog post for building a custom App for the AdminService.#You TECHNICALLY can use the Native Client App that gets created when you build your CMG, but it's #Better to create a custom App instead of hijacking the built in one.FunctionGet-AADAuthToken {
Param (
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Native App) - Overview. Copy the Application (client) ID")]
[string]
$ClientID,
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Native App) - Overview. Copy the Directory (tenant) ID")]
[string]
$TenantID,
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Cloud Management App) - Authentication. Copy the Redirect URI. It should start with ms-appx-web://")]
[string]
$RedirectURI,
[Parameter(Mandatory=$True, HelpMessage = "Home > App registrations > (Your Cloud Management App) - Expose an API. Copy the Application ID URI")]
[string]
$ResourceAppIdURI,
[Parameter(Mandatory=$True, HelpMessage = "Change the prompt behavior to force credentials each time. https://msdn.microsoft.com/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx.")]
[ValidateSet("Auto", "Always", "Never", "RefreshSession")]
[string]
$PromptForNewCredentials = "Auto" )
#Get AAD Token for AdminServiceIf(Test-Path"$($PSScriptRoot)\Auth.Json") {
$AuthToken = Get-Content -Path "$($PSScriptRoot)\Auth.Json" | ConvertFrom-JsonIf(($AuthToken-and (Get-Date) -lt$AuthToken.ExpiresOn)) {
Return$AuthToken }
}
If(($AuthToken-and (Get-Date) -ge$AuthToken.ExpiresOn) -or (!(Test-Path"$($PSScriptRoot)\Auth.Json"))) {
$Authority = "https://login.microsoftonline.com/$($TenantID)/oauth2/v2.0/authorize"$AadModule = Get-Module -Name "AzureAD" -ListAvailable
If ($AadModule-eq $null)
{
Write-Host"AzureAD PowerShell module not found, looking for AzureADPreview"$AadModule = Get-Module -Name "AzureADPreview" -ListAvailable
}
If ($AadModule-eq $null)
{
Write-Error"AzureAD Powershell module not installed..."Write-Error"Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" Exit
}
If ($AadModule.count -gt1) {
$Latest_Version = ($AadModule | Select-Object version | Sort-Object)[-1]
$aadModule = $AadModule | ForEach-Object { $_.version -eq$Latest_Version.version }
If ($AadModule.count -gt1)
{
$aadModule = $AadModule | Select-Object -Unique
}
$adal = Join-Path$AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"$adalforms = Join-Path$AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll" }
Else {
$adal = Join-Path$AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"$adalforms = Join-Path$AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll" }
[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-NullTry {
$authContext = New-Object"Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority$platformParameters = New-Object"Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList $PromptForNewCredentials$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $clientId, $redirectUri, $platformParameters).Result
# If the accesstoken is valid save a Json FileIf ($authResult.AccessToken) {
$authResult | ConvertTo-Json | Out-File -FilePath "$($PSScriptRoot)\Auth.Json" -Force
return$authResult }
Else {
Write-Error"Authorization Access Token is null, please re-run authentication..."break }
}
Catch {
Write-Error$_.Exception.Message
Write-Error$_.Exception.ItemName
Break }
}
}
& $Main
If you did it right, you should have just triggered a client action on a co-managed device. I haven’t been able to test a non-co-managed device, but I expect that the functionality we saw demoed at Ignite will bring that functionality along once it is released.
Summary
The purpose of this post was to show you that you can trigger WMI methods with the ConfigMgr AdminService in the 1910 CB release of ConfigMgr. Hopefully it has inspired new ideas for how you may use this moving forward. I’ve already spoken with a few folks who are looking at making standalone admin web frontends or leveraging the Power Platform apps to interact directly with ConfigMgr using AdminService. The possibilities are truly limitless! One last note - the AdminService is still very much a work-in-progress. If you plan to mess with it, I wouldn’t start building business processes around it just yet - I think it will get better and easier to interact with moving forward. But if you want to just start getting your hands dirty, get in and give it a test drive.