8 min read

ConfigMgr - Set custom device properties using CM AdminService / RestAPI

ConfigMgr - Set custom device properties using CM AdminService / RestAPI

With the release of MECM 2107 came the possibility to add custom properties to an device object, which is nice if you want to stop eg. tattooing devices with registry-keys for reporting, collections etc etc, but as you could not view these properties in any other way than GET with the AdminService, it could be difficult to get an overview.

With the arrival of MECM version 2111, there now is an tab on each device, where you can easly view, edit, create new or remove these properties straight from the console, YAY!

What’s new in version 2111 - Configuration Manager
Get details about changes and new capabilities introduced in version 2111 of Configuration Manager current branch.

As someone which is new to RestAPI's and such, i think the MS page about the AdminService, and how to set the Custom Properties using it, is quite lacking in information and a bit hard to grasp.
But, with some reading and looking in to other things which uses it, i managed to put something together which works, and i thought i would document it and post it here for others to get inspired by!

About the script

Prerequisites

You will need an account with "Modify" permissions on device resources

I choose to create a separate "Service Account" in the domain for this, keep the rights to a bare minimum, give the account a strong password and then add the account to "Administrative Users" in CM console.

In Configuration Manager i choose to create a new role with the bare minimum rights in order to minimise risks.
To create/read/modify custom properties, you will need the following in a role:

  • To set properties: Modify Resource
  • To view properties: Read Resource
  • To remove properties: Delete Resource

I created this custom role and if you want to import it with the same rights, here is an XML definition of it, copy, paste in notepad/notepad++ and save as *.xml

<SMS_Roles>
  <SMS_Role CopiedFromID="SMS0001R" RoleName="CustomDevicePropertiesModify" RoleDescription="">
    <Operations>
      <Operation GrantedOperations="4225" ObjectTypeID="1" />
    </Operations>
  </SMS_Role>
</SMS_Roles>

Go to "\Administration\Overview\Security\Security Roles" -> Right-Click -> Import Security Role -> Select the *.XML

The Script - to test on specific devices

So, the whole script i came up with looks like below. This version of the script is to run outside of CM, like if you want to test it on some devices.

What you need to change:

$Devices - Add the name of the devices that you want to test it on
#JSON Attributes - Add your own attributes and change the #JSON Body accordingly


Param(
    [Parameter(HelpMessage = 'Username of the user with sufficent rights to edit Device resources, must conaint "DOMAIN\USERNAME"', Mandatory = $True)]
    $Username,
    
    [Parameter(HelpMessage = 'Password for user account', Mandatory = $True)]
    $Password,

    [Parameter(HelpMessage = 'Your siteserver FQDN for AdminService URl, eg "cm01.corp.viamonstra.com"', Mandatory = $True)]
    [string]$SiteServerFQDN
)

$SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force 
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $SecurePassword

##Device names
$Devices = (
    "Test02",
    "Test03",
    "Test04"
)

##Set JSON Key values
    $Location = "Norrkoping"
    $Department = "IT"
    $Billing = "1234"

#Create the JSON body Key/value pairs
$JSONRequest = @"
{
  "ExtensionData": {
    "Department":"$Department",
    "Location":"$Location",
    "Billing":"$Billing"
  }
}
"@

## This part about the cert ignore is lifted from ConfigMgr Modern Driver Management Script. All cred to those guys!
# Attempt to ignore self-signed certificate binding for AdminService
# Convert encoded base64 string for ignore self-signed certificate validation functionality
$CertificationValidationCallbackEncoded = "DQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0AOwANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB1AHMAaQBuAGcAIABTAHkAcwB0AGUAbQAuAE4AZQB0ADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAZQBjAHUAcgBpAHQAeQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHUAcwBpAG4AZwAgAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBYADUAMAA5AEMAZQByAHQAaQBmAGkAYwBhAHQAZQBzADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAcAB1AGIAbABpAGMAIABjAGwAYQBzAHMAIABTAGUAcgB2AGUAcgBDAGUAcgB0AGkAZgBpAGMAYQB0AGUAVgBhAGwAaQBkAGEAdABpAG8AbgBDAGEAbABsAGIAYQBjAGsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAewANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHAAdQBiAGwAaQBjACAAcwB0AGEAdABpAGMAIAB2AG8AaQBkACAASQBnAG4AbwByAGUAKAApAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAewANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAaQBmACgAUwBlAHIAdgBpAGMAZQBQAG8AaQBuAHQATQBhAG4AYQBnAGUAcgAuAFMAZQByAHYAZQByAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBWAGEAbABpAGQAYQB0AGkAbwBuAEMAYQBsAGwAYgBhAGMAawAgAD0APQBuAHUAbABsACkADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAUwBlAHIAdgBpAGMAZQBQAG8AaQBuAHQATQBhAG4AYQBnAGUAcgAuAFMAZQByAHYAZQByAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBWAGEAbABpAGQAYQB0AGkAbwBuAEMAYQBsAGwAYgBhAGMAawAgACsAPQAgAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAZABlAGwAZQBnAGEAdABlAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAKAANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAATwBiAGoAZQBjAHQAIABvAGIAagAsACAADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAFgANQAwADkAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBlAHIAdABpAGYAaQBjAGEAdABlACwAIAANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAWAA1ADAAOQBDAGgAYQBpAG4AIABjAGgAYQBpAG4ALAAgAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIABTAHMAbABQAG8AbABpAGMAeQBFAHIAcgBvAHIAcwAgAGUAcgByAG8AcgBzAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAKQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHIAZQB0AHUAcgBuACAAdAByAHUAZQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AA0ACgAgACAAIAAgACAAIAAgACAA"
$CertificationValidationCallback = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($CertificationValidationCallbackEncoded))


# Load required type definition to be able to ignore self-signed certificate to circumvent issues with AdminService running with ConfigMgr self-signed certificate binding
Add-Type -TypeDefinition $CertificationValidationCallback
[ServerCertificateValidationCallback]::Ignore()


$DeviceResourceIDs = @()

##Get Device MachineID using AdminService
Foreach($Device in $Devices){
    $DeviceInfoURL = "https://$SiteServerFQDN/AdminService/v1.0/Device?`$filter=startswith(Name,'$Device')"
    $DeviceInf = Invoke-RestMethod -Method 'GET' -Uri $DeviceInfoURL -Credential $Credentials
    $DeviceResourceIDs += $DeviceInf.value | select -ExpandProperty "MachineId"
}

#Create/modify the Custom properties
Foreach($DeviceResourceID in $DeviceResourceIDs){
    $URI = "https://$SiteServerFQDN/AdminService/v1.0/Device($DeviceResourceID)/AdminService.SetExtensionData"
    Invoke-RestMethod -Method 'POST' -Uri "$URI" -Credential $Credentials -Body $JSONRequest -ContentType "application/json"
}

Breaking it down

Authentication

First "The account that makes the API calls requires the following permissions on a collection that contains the target device:

  • To set properties: Modify Resource
  • To view properties: Read Resource
  • To remove properties: Delete Resource"

If it's to be used in an TS these credentials could be predefined using a "Set Dynamic Variabled" step, and hide the input, and then you just get them from the TS variables, i'll go through how to do this later on!

Predefined in the script (NOT(!) Recommended!):

#Credentials used to identify with the AdminService
$UserName = "VIAMONSTRA\Administrator"
$Password = 'P@ssw0rd' | ConvertTo-SecureString -AsPlainText -Force


$Credentials = New-Object -TypeName System.Management.Automation.PSCredential `
 -ArgumentList $UserName, $Password

Getting the properties

##Set JSON Key values
    $Location = "Norrkoping"
    $Department = "IT"
    $Billing = "1234"

#Create the JSON body Key/value pairs
$JSONRequest = @"
{
  "ExtensionData": {
    "Department":"$Department",
    "Location":"$Location",
    "Billing":"$Billing"
  }
}
"@

Here in this example, i have predefined the properties to be created/set in the script, and through Dynamic Variables, while in "real" OSD scenario, you might have an FrontEnd HTA of some sort, at the beginning of the TS where a user choose things like this and the variables gets created during the install.

This is an example FrontEnd HTA i made for another scenario, the properties don't add up, but you catch my drift.

The Certificate part

This part is completely ripped-off from the 'Modern Driver Management' "Invoke-ApplyDriver.ps1"-script from Nickolaj, as i didnt know how to get this to work without it without having a "real" certificate and not the self-signed from MECM, so, all the cred to those guys!

DeviceResourceID + Set properties

This was for me the hard part, as I've never worked with RestAPI's before

$DeviceResourceIDs = @()

##Get Device MachineID using AdminService
Foreach($Device in $Devices){
    $DeviceInfoURL = "https://$SiteServerFQDN/AdminService/v1.0/Device?`$filter=startswith(Name,'$Device')"
    $DeviceInf = Invoke-RestMethod -Method 'GET' -Uri $DeviceInfoURL -Credential $Credentials
    $DeviceResourceIDs += $DeviceInf.value | select -ExpandProperty "MachineId"
}

#Create/modify the Custom properties
Foreach($DeviceResourceID in $DeviceResourceIDs){
    $URI = "https://$SiteServerFQDN/AdminService/v1.0/Device($DeviceResourceID)/AdminService.SetExtensionData"
    Invoke-RestMethod -Method 'POST' -Uri "$URI" -Credential $Credentials -Body $JSONRequest -ContentType "application/json"
}

The first part uses the AdminService to fetch the "DeviceResourceID" from the Device-object in MECM, as the CM powershell-module isnt available in WinPE/TS (if you haven't included it in the bootimage) you need to use the AdminService to first get the DeviceResourceID, based on the DeviceName which you get either from PowerShell and then query WMI for maybe SerialNumber (if this is what you use for DeviceName) or you can use powershell and call the "OSDComputerName" TSVariable.

Next, we use the DeviceResourceID to brand the device with our JSON name-value-pair, and were done!

The Script - In an task sequence environment

##Load Pre-requisites for Tasksquence use.
$TSEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment

##Get AdminService FQDN from Dynamic Variable
$SiteServerFQDN = $TSEnvironment.Value("AdminService")

#Credentials used to identify with the AdminService using TSEnvironment Dynamic variables
$Username = $TSEnvironment.Value("APIUsername")
$SecurePassword = $TSEnvironment.Value("APIPassword") | ConvertTo-SecureString -AsPlainText -Force 

#$SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force 
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $SecurePassword


##Get TS env Values
$Department = $TSEnvironment.Value("Department")
$Location = $TSEnvironment.Value("Location")
$Billing = $TSEnvironment.Value("Billing")

#Create the JSON body with attributes
$JSONRequest = @"
{
  "ExtensionData": {
    "Department":"$Department",
    "Location":"$Location",
    "Billing":"$Billing"
  }
}
"@

# Attempt to ignore self-signed certificate binding for AdminService
# Convert encoded base64 string for ignore self-signed certificate validation functionality
$CertificationValidationCallbackEncoded = "DQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0AOwANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB1AHMAaQBuAGcAIABTAHkAcwB0AGUAbQAuAE4AZQB0ADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAZQBjAHUAcgBpAHQAeQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHUAcwBpAG4AZwAgAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AQwByAHkAcAB0AG8AZwByAGEAcABoAHkALgBYADUAMAA5AEMAZQByAHQAaQBmAGkAYwBhAHQAZQBzADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAcAB1AGIAbABpAGMAIABjAGwAYQBzAHMAIABTAGUAcgB2AGUAcgBDAGUAcgB0AGkAZgBpAGMAYQB0AGUAVgBhAGwAaQBkAGEAdABpAG8AbgBDAGEAbABsAGIAYQBjAGsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAewANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHAAdQBiAGwAaQBjACAAcwB0AGEAdABpAGMAIAB2AG8AaQBkACAASQBnAG4AbwByAGUAKAApAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAewANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAaQBmACgAUwBlAHIAdgBpAGMAZQBQAG8AaQBuAHQATQBhAG4AYQBnAGUAcgAuAFMAZQByAHYAZQByAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBWAGEAbABpAGQAYQB0AGkAbwBuAEMAYQBsAGwAYgBhAGMAawAgAD0APQBuAHUAbABsACkADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAUwBlAHIAdgBpAGMAZQBQAG8AaQBuAHQATQBhAG4AYQBnAGUAcgAuAFMAZQByAHYAZQByAEMAZQByAHQAaQBmAGkAYwBhAHQAZQBWAGEAbABpAGQAYQB0AGkAbwBuAEMAYQBsAGwAYgBhAGMAawAgACsAPQAgAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAZABlAGwAZQBnAGEAdABlAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAKAANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAATwBiAGoAZQBjAHQAIABvAGIAagAsACAADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAFgANQAwADkAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBlAHIAdABpAGYAaQBjAGEAdABlACwAIAANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAWAA1ADAAOQBDAGgAYQBpAG4AIABjAGgAYQBpAG4ALAAgAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIABTAHMAbABQAG8AbABpAGMAeQBFAHIAcgBvAHIAcwAgAGUAcgByAG8AcgBzAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAKQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHIAZQB0AHUAcgBuACAAdAByAHUAZQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AA0ACgAgACAAIAAgACAAIAAgACAA"
$CertificationValidationCallback = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($CertificationValidationCallbackEncoded))


# Load required type definition to be able to ignore self-signed certificate to circumvent issues with AdminService running with ConfigMgr self-signed certificate binding
Add-Type -TypeDefinition $CertificationValidationCallback
[ServerCertificateValidationCallback]::Ignore()


#DeviceName
$Device = $TSEnvironment.Value("_SMSTSMachineName")

#Get DeviceInfo using AdminService
$DeviceInfoURL = "https://$SiteServerFQDN/AdminService/v1.0/Device?`$filter=startswith(Name,'$Device')"
$DeviceInf = Invoke-RestMethod -Method 'GET' -Uri $DeviceInfoURL -Credential $Credentials
$DeviceResourceID = $DeviceInf.value | select -ExpandProperty "MachineId"

#Create/set/modify the Custom properties
$URI = "https://$SiteServerFQDN/AdminService/v1.0/Device($DeviceResourceID)/AdminService.SetExtensionData"
Invoke-RestMethod -Method 'POST' -Uri "$URI" -Credential $Credentials -Body $JSONRequest -ContentType "application/json"

Setup

If you have created the account and role before, this is what you have to do in order to get it to work in a task sequence, i will explain it with a new TS, just for this example:

  1. Create a new "Set Dynamic Variables" -step and add two new custom variables, "APIUserName" & "APIPassword".
    APIUserName = DOMAIN\USERNAME (eg. "VIAMONSTRA\CM_CDP" in my case)

    Create another custom variable named "AdminService" and enter the FQDN of your MP with AdminService on it, eg. "cm01.corp.viamonstra.com"

    You should end up with something like this

2. Create another "Set Dynamic Variables" -step, and if you just use the example script supplied bellow, you need to create three new variables as such, enter your own information in each, or create your own (just remember to edit the script if you do so, under "##Get TS Env Values" and "## Json body")

3. Add a "Run Powershell Script" -step
3.1 Choose and Click "Enter Script", then Copy/Paste the script below, make any changes to the JSON Body if you need to.
3.2 Change "Powershell Execution policy" -> Bypass

And now you are all set to try it out!

References

Custom properties for devices - Configuration Manager
Use the administration service to set custom property data on devices, for reporting or collections.
What is the administration service - Configuration Manager
Use the Configuration Manager administration service REST API to interact with the site over an HTTPS OData connection.
GitHub - Love-A/EndpointManagement: Endpoint Management stuff galore!
Endpoint Management stuff galore! Contribute to Love-A/EndpointManagement development by creating an account on GitHub.