Intune: Graph API JSON Batching

Based on feedback from MVPs for the post on Grouping based on Hardware Inventory data, I sat down this weekend to get my hands on JSON batching and learn about it. It took me couple of hours to narrow down the syntax and finally when I ran the script with JSON batching for updating an extension attribute with a Processor Architecture property and compared the time taken with my earlier script, the result was astonishing. In my lab with 7 devices, the script with batching took less than 3 seconds to process the data compared to 10+ seconds taken by the script without batching. I would love to hear from you on how long it took for all the devices in your environment.

To start with, the script remains same until you get Intune managed devices (Line #24 – all or per platform or any condition). Additionally, a full sync in a large environment is a very costly operation and often takes more than an hour. So I have added a parameter $OffsetHours to process only the devices which have sync’ed in last X hours. Configure the $LogFile, $Offsethours, $ExtensionAttributeNumber, $HWInventoryPropertyName in Line #9 - #12 respectively. Once you get the list of managed devices sync’ed in last X hours, next step would be to iterate each device to get the hardware inventory data. Going by the JSON batching documentation, the body should contain id (string), supported html method, graph uri while headers and body are required only if we use PATCH or POST method.

Step 1: Obtain Hardware Inventory for all Managed Devices:

In this step, you will obtain the hardware inventory information for all the managed devices. You must use “GET” method (remember, it is case sensitive!) and make sure to use “$select” statement in URI to get extended properties. Also, I recommend using the id as number starting from 0 reflecting the array object id of each managed device.

Since the JSON batching supports only 20 reports at a time for parallel execution, You need to keep track of the count and once you have 20 requests in the body, you should convert it in to JSON and invoke Graph Request to obtain the results. The results are then stored in $HWResults array variable and you move to process the next 20. You need to repeat this untill all managed devices are processed. At the end of this step, $HWResults will have hardware inventory data for all the managed devices.

Step 2: Update Extension Attribute of corresponding Entra Device:

In this step, you will update the extension attribute of corresponding Entra device object for all the managed devices. You must use “PATCH” method (remember, not POST method!) and make sure you have headers and body information in the batch request body. The id values and the the batching follows similar pattern as Step 1. At the end of this step, $UpdateResults will have the update status for all the managed devices.

Step 3: Extract Update results to a CSV file for review

Final step is to run through the $UpdateResults variable and extract the update status information and write it to a log file configured in Line #21. I have added a stopwatch utility to track and display the time taken for completion of script.

Script:

Here is a sample script putting all pieces together. Depending on the exact property, the script might need few changes.

You need to schedule this script using task scheduler based on how frequently the data could change.

When you schedule the script, the time between the two executions should be less than the $OffsetHours to ensure we do not miss any data. Smaller the Offset Hour, less device data to process but more frequently.

Example: If $OffsetHours is 2, schedule the script to run every hour

Once you understand the framework, you can build your own scripts to solve your specific needs.

Note: In all my sample scripts, I use delegated permissions. If you to run the script unattended in any automation tool, modify the script to levarge client app registration along with client secret – reference post

<#
DISCLAIMER STARTS 
THIS SAMPLE CODE IS PROVIDED FOR THE PURPOSE OF ILLUSTRATION ONLY AND IS NOT INTENDED TO BE USED IN A PRODUCTION ENVIRONMENT. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED IS "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE."
DISCLAIMER ENDS
#>


#Set Variables
$Now = Get-Date -Format "dd-MM-yyyy-HH-mm-ss"
$LogFile = "C:\Windows\Temp\DeviceHWInfo_Update_Log_$($Now).log"
$ExtensionAttributeNumber = "extensionAttribute5"
$HWInventoryPropertyName = "processorArchitecture"
$OffsetHours = 2


$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
#Connect to Microsoft Graph using delegated permissions
Write-Output "Connecting Microsoft Graph..."
Connect-MgGraph -Scopes DeviceManagementManagedDevices.Read.All,Directory.AccessAsUser.All

$CutOffDate = (Get-Date).AddHours(-$OffsetHours)
$CutOffDate = ([string](Get-Date $CutOffDate -Format "yyyy-MM-dd HH:mm:ss") -replace " ","T") + "z"
#Get List of Managed Devices. Here the platform is scoped to Android.
Write-Output "Getting List of Managed Devices updated in last $OffsetHours..."
$ManagedDevices = Get-MgbetaDeviceManagementManagedDevice -Filter "lastSyncDateTime ge $CutOffDate" -All

#Get Total Devices Count
$TotalDevices = $ManagedDevices.count

#Initialize few variables
$HWResults = @()
$PSObject = @()
$Set = 1

#Loop all devices to get hardware information. Currently JSON batching supports only 20 requests at a time. So breaking down the total devices by set of 20 each and getting the Hardware Information and storing the output in $HWResults array.

Write-Output "Getting Hardware Information of all managed devices..."

For ($n = 1; $n -le $TotalDevices; $n++){
	Write-Progress -Activity "Getting Hardware Information of all managed devices..." -Status "Processing $n out of $TotalDevices" -PercentComplete $($n*100/$TotalDevices)
	$id = $n - 1
	$DeviceId = $ManagedDevices[$id].id
	$PSObject += [PSCustomObject]@{
        id      = "$id"
        method  = "GET"
        url     = "/deviceManagement/managedDevices/$($DeviceId)?`$select=hardwareInformation,$HWInventoryPropertyName"
    }
	if (($n -eq $($Set*20)) -or ($n -eq $TotalDevices)){
		$BatchRequestBody = [PSCustomObject]@{requests = $PSObject }
		$JSONRequests = $BatchRequestBody | ConvertTo-Json -Depth 4
		$HWResults += Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/beta/$batch' -Body $JSONRequests -ContentType 'application/json' -ErrorAction Stop
		$PSObject = @()
		$Set ++
	}
}

#Initialize few variables
$UpdateResults = @()
$PSObject = @()
$Set = 1

#Loop all devices to update corresponding Entra Device Object. Currently JSON batching supports only 20 requests at a time. So breaking down the total devices by set of 20 each and updating the corresponding Entra device object and storing the results in $UpdateResults array.

Write-Output "Updating Extension attribute of all the devices..."

For ($n = 1; $n -le $TotalDevices; $n++){
	Write-Progress -Activity "Updating Extension attribute of all the devices..." -Status "Processing $n out of $TotalDevices" -PercentComplete $($n*100/$TotalDevices)
	$id = $n - 1
	$PropertyValue = ($HWResults.responses | where {$_.id -eq $id}).body.$HWInventoryPropertyName
	$DeviceId = $ManagedDevices[$id].AzureADDeviceId
	$PSObject += [PSCustomObject]@{
        id      = "$id"
        method  = "PATCH"
        url	    = "/devices(deviceId=`'$DeviceId`')"
		headers = @{
				"Content-Type" = "application/json"
			}
		body 	= @{
				"extensionAttributes" = @{
				"$ExtensionAttributeNumber" = "$PropertyValue"
			}
		}
    }
	if (($n -eq $($Set*20)) -or ($n -eq $TotalDevices)){
		$BatchRequestBody = [PSCustomObject]@{requests = $PSObject }
		$JSONRequests = $BatchRequestBody | ConvertTo-Json -Depth 5
		$UpdateResults += Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/beta/$batch' -Body $JSONRequests -ContentType 'application/json' -ErrorAction Stop
		$PSObject = @()
		$Set ++
	}
}

#Create Update Status Report

Write-Output "Creating Update Status Report..."

Add-Content -Path $LogFile -Value "S.No,IntuneDeviceId,AzureADDeviceId,DeviceName,$HWInventoryPropertyName,UpdateStatus"

For ($n = 1; $n -le $TotalDevices; $n++){
	Write-Progress -Activity "Creating Update Status Report..." -Status "Processing $n out of $TotalDevices" -PercentComplete $($n*100/$TotalDevices)
	$id = $n - 1
	$DeviceName = $ManagedDevices[$id].DeviceName
	$IntuneDeviceId = $ManagedDevices[$id].Id
	$AzureADDeviceId = $ManagedDevices[$id].AzureADDeviceId
	$PropertyValue = ($HWResults.responses | where {$_.id -eq $id}).body.$HWInventoryPropertyName
	$UpdateStatus = ($UpdateResults.responses | where {$_.id -eq $id}).status
	If ($UpdateStatus -eq 204) { $UpdateStatus = "Success" }
	Add-Content -Path $LogFile -Value "$n,$IntuneDeviceId,$AzureADDeviceId,$DeviceName,$PropertyValue,$UpdateStatus"
}

$Stopwatch.Stop()

Write-Output "Time Taken for the script execution with batching:`n"

$Stopwatch.Elapsed

One thought on “Intune: Graph API JSON Batching

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.