Push Every Tenant's Intune Devices Into IT Glue With One PowerShell Script
TL;DR
- A single PowerShell script can create an Intune Devices flexible asset in IT Glue and populate it with every enrolled device across all customers, creating new device entries or updating existing ones on each run.
- Each documented device captures name, ownership, OS and version, compliance state, user, Autopilot enrollment, encryption status, serial number, and any matched IT Glue configuration.
- The script uses the Secure Application Model for a headless connection into every customer tenant, so it reads Intune data across all clients without per-tenant logins.
- The Azure AD app registration needs DeviceManagementConfiguration.Read.All and DeviceManagementManagedDevices.Read.All; tenants without Intune licensing or missing those permissions are caught and skipped.
As device management shifts to Intune, the question every multi-tenant MSP eventually asks is simple: where is the single view of every device, across every customer, with the metadata that actually matters? Clicking through Endpoint Manager tenant by tenant does not scale, and the compliance and Autopilot details you care about are buried a few clicks deep on each one. The fix is to let the documentation build itself.
The approach here is a PowerShell script that creates a new flexible asset in IT Glue and populates it with every enrolled device, per company. It creates new device entries or updates existing ones on each run, so the documentation stays current instead of going stale the day after you write it. For every device, you end up documenting:
- Device Name
- Ownership (Corporate or Personal)
- OS
- OS Version
- Compliance State
- User
- Autopilot Enrolled
- Encrypted
- Serial Number
- Configurations (if existing)


What you need before you run it
You will need tokens and GUIDs from both the Secure Application Model and IT Glue. The Secure Application Model is what allows a headless connection into all of your customer environments. The script to set that up comes from Kelvin at CyberDrain: the Create-SecureAppModel script on GitHub (opens in new tab).
In IT Glue, create a new API key. IT Glue's documentation on generating an API key (opens in new tab) covers the steps.
In Microsoft, make sure the app created with the Secure Application Model has the following permissions, if they are not already present:
- DeviceManagementConfiguration.Read.All
- DeviceManagementManagedDevices.Read.All
Reference IT Glue's API documentation (opens in new tab) for adding permissions to your app registration.
The script
The full script lives in the IT-Glue repository on GitHub (opens in new tab).
Param
(
[cmdletbinding()]
[Parameter(Mandatory= $true, HelpMessage="Enter your ApplicationId from the Secure Application Model https://github.com/KelvinTegelaar/SecureAppModel/blob/master/Create-SecureAppModel.ps1")]
[string]$ApplicationId,
[Parameter(Mandatory= $true, HelpMessage="Enter your ApplicationSecret from the Secure Application Model")]
[string]$ApplicationSecret,
[Parameter(Mandatory= $true, HelpMessage="Enter your Partner Tenantid")]
[string]$tenantID,
[Parameter(Mandatory= $true, HelpMessage="Enter your refreshToken from the Secure Application Model")]
[string]$refreshToken,
[Parameter(Mandatory= $true, HelpMessage="Enter your IT Glue API Key")]
[string]$APIKey,
[Parameter(Mandatory= $true, HelpMessage="Enter your IT Glue URL, ex: https://api.itglue.com")]
[string]$APIEndpoint
)
###Additional API Permissions Need for App in Azure AD ####
####DeviceManagementConfiguration.Read.All
####DeviceManagementManagedDevices.Read.All
###MICROSOFT SECRETS#####
$ApplicationId = $ApplicationId
$ApplicationSecret = $ApplicationSecret
$tenantID = $tenantID
$refreshToken = $refreshToken
$secPas = $ApplicationSecret| ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $secPas)
########################## IT-Glue Information ############################
$APIKey = $APIKey
$APIEndpoint = $APIEndpoint
$FlexAssetName = "Intune Devices"
$Description = "Documentation for all devices enrolled into Intune"
write-host "Getting IT-Glue module" -ForegroundColor Green
If (Get-Module -ListAvailable -Name "ITGlueAPI") {
Import-module ITGlueAPI
}
Else {
Install-Module ITGlueAPI -Force
Import-Module ITGlueAPI
}
#Settings IT-Glue logon information
Add-ITGlueBaseURI -base_uri $APIEndpoint
Add-ITGlueAPIKey $APIKEy
write-host "Checking if Flexible Asset exists in IT-Glue." -foregroundColor green
$FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data
if (!$FilterID) {
write-host "Does not exist, creating new." -foregroundColor green
$NewFlexAssetData =
@{
type = 'flexible-asset-types'
attributes = @{
name = $FlexAssetName
icon = 'laptop'
description = $description
}
relationships = @{
"flexible-asset-fields" = @{
data = @(
@{
type = "flexible_asset_fields"
attributes = @{
order = 1
name = "Device Name"
kind = "Text"
required = $true
"show-in-list" = $true
"use-for-title" = $true
}
},
@{
type = "flexible_asset_fields"
attributes = @{
order = 2
name = "Ownership"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 3
name = "OS"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 4
name = "OS Version"
kind = "Text"
required = $false
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 5
name = "Compliance State"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 6
name = "User"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 7
name = "Autopilot Enrolled"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 8
name = "Encrypted"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 9
name = "Serial Number"
kind = "Text"
required = $true
"show-in-list" = $true
}
}
@{
type = "flexible_asset_fields"
attributes = @{
order = 10
name = "Configurations"
'tag-type' = "Configurations"
kind = "Tag"
required = $false
"show-in-list" = $true
}
}
)
}
}
}
New-ITGlueFlexibleAssetTypes -Data $NewFlexAssetData
$FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data
}
#Grab all IT-Glue contacts to match the domain name.
write-host "Getting IT-Glue contact list" -foregroundColor green
$i = 1
$AllITGlueContacts = do {
$Contacts = (Get-ITGlueContacts -page_size 1000 -page_number $i).data.attributes
$i++
$Contacts
Write-Host "Retrieved $($Contacts.count) Contacts" -ForegroundColor Yellow
}while ($Contacts.count % 1000 -eq 0 -and $Contacts.count -ne 0)
$i=1
$AllITGlueConfigurations = do {
$Configs = (Get-ITGlueConfigurations -page_size 1000 -page_number $i).data
$i++
$Configs
Write-Host "Retrieved $($Configs.count) Configurations" -ForegroundColor Yellow
}while ($Configs.count % 1000 -eq 0 -and $Configs.count -ne 0)
$DomainList = foreach ($Contact in $AllITGlueContacts) {
$ITGDomain = ($contact.'contact-emails'.value -split "@")[1]
[PSCustomObject]@{
Domain = $ITGDomain
OrgID = $Contact.'organization-id'
Combined = "$($ITGDomain)$($Contact.'organization-id')"
}
}
###Connect to your Own Partner Center to get a list of customers/tenantIDs #########
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
Write-Host "Found $($customers.Count) customers in Partner Center." -ForegroundColor DarkGreen
foreach ($customer in $customers) {
Write-Host "Found $($customer.Name) in Partner Center" -ForegroundColor Green
###Get Access Token########
$CustomerToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -Tenant $customer.TenantID
$headers = @{ "Authorization" = "Bearer $($CustomerToken.AccessToken)" }
$Devices = ""
#####Get Intune information if it is available####
try{
$Devices = (Invoke-RestMethod -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDevices' -Headers $headers -Method Get -ContentType "application/json").value | Select-Object deviceName, ownerType, operatingSystem, osVersion, complianceState,userPrincipalName, autopilotEnrolled,isEncrypted, serialNumber
}catch{('This tenant either does not have intune licensing or you have not added the correct permissions to the which are listed in the begining of this script')}
if($Devices){
forEach($device in $Devices){
forEach($configuration in $AllITGlueConfigurations){
if($configuration.attributes.'serial-number' -eq $device.serialNumber){
$configExist = $configuration
Write-Host "Existing Configuration Found in ITGlue" -ForegroundColor Cyan
} else {
$configExist = ""
}
}
$FlexAssetBody =
@{
type = 'flexible-assets'
attributes = @{
traits = @{
'device-name' = $device.deviceName
'ownership' = $device.ownertype
'os' = $device.operatingSystem
'os-version' = $device.osVersion
'compliance-state' = $device.complianceState
'user' = $device.userPrincipalName
'autopilot-enrolled' = $device.autopilotEnrolled | Out-String
'encrypted' = $device.isEncrypted | Out-String
'serial-number' = $device.serialNumber
'configurations' = $configExist.id
}
}
}
Write-Host "Finding $($customer.name) in IT-Glue" -ForegroundColor Green
$CustomerDomains = Get-MsolDomain -TenantId $Customer.TenantId
$orgid = foreach ($customerDomain in $customerdomains) {
($domainList | Where-Object { $_.domain -eq $customerDomain.name }).'OrgID'
}
$orgID = $orgid | Select-Object -Unique
if(!$orgID){
Write-Host "$($customer.name) does not exist in IT-Glue" -ForegroundColor Red
}
if($orgID){
$ExistingFlexAsset = (Get-ITGlueFlexibleAssets -filter_flexible_asset_type_id $($filterID.id) -filter_organization_id $orgID).data | Where-Object { $_.attributes.traits.'serial-number' -eq $device.serialNumber}
#If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just update.
if (!$ExistingFlexAsset) {
$FlexAssetBody.attributes.add('organization-id', $orgID)
$FlexAssetBody.attributes.add('flexible-asset-type-id', $($filterID.ID))
write-host "Creating Intune Device $($device.deviceName) for $($customer.name) into IT-Glue organisation $org" -ForegroundColor Green
New-ITGlueFlexibleAssets -data $FlexAssetBody
}
else {
write-host "Updating Intune Device $($device.deviceName) for $($customer.name) into IT-Glue organisation $org" -ForegroundColor Yellow
$ExistingFlexAsset = $ExistingFlexAsset | select-object -last 1
Set-ITGlueFlexibleAssets -id $ExistingFlexAsset.id -data $FlexAssetBody
}
}
}}}How the matching logic works
A few decisions in the script are worth calling out, because they shape what you get in IT Glue:
- Headless multi-tenant read. The script pulls a partner access token, lists every customer with
Get-MsolPartnerContract -All, then requests a per-customer Graph token and reads the betamanagedDevicesendpoint for each tenant in turn. - Graceful skip on no Intune. The Graph call is wrapped in a try-catch. A tenant without Intune licensing, or an app registration missing the two required permissions, gets a written notice and the loop moves on instead of erroring out.
- Org matching by email domain. Customer tenant domains are matched against IT Glue contact email domains to find the right organization ID. If no match is found, the script reports that the customer does not exist in IT Glue.
- Create or update by serial. Within an org, the script checks whether a flexible asset already exists for that serial number. If not, it adds the organization ID and flexible-asset-type ID and creates a new asset; if it exists, it updates the last matching one.
One open design question
The script soft-matches on serial number to tie an Intune-enrolled device to an existing IT Glue configuration and tags it onto the asset. One idea we have not committed to: creating Intune devices as brand-new IT Glue configurations when that serial-number soft match fails. There is a reasonable argument either way, and the same goes for additional device metadata worth surfacing on the asset. If you have a strong opinion on either, that feedback is welcome.
Frequently asked questions
How does the script match an Intune device to an existing IT Glue configuration?
It soft-matches on serial number. If an IT Glue configuration shares the device's serial number, the script tags that configuration onto the flexible asset. If no match is found, the configuration field is left empty.
What happens on tenants that do not have Intune?
The Graph call for managed devices is wrapped in a try-catch. If a tenant lacks Intune licensing or the app registration is missing the required permissions, the script writes a message and moves on to the next customer rather than failing.
What Microsoft Graph permissions does the app registration need?
DeviceManagementConfiguration.Read.All and DeviceManagementManagedDevices.Read.All, added to the app created with the Secure Application Model. The script reads device data from the Graph beta managedDevices endpoint.
Documentation is a snapshot. Posture is the moving target.
This script tells you what is enrolled. It will not tell you which of those devices quietly fell out of compliance last week. CloudCapsule checks 250+ Microsoft 365 controls per tenant in about 60 seconds.
See the features
Written by
Nick Ross
CEO · Microsoft MVP · Founder, T-Minus 365
Nick is not just a CEO, he's a respected thought leader and influencer in the MSP space. Tens of thousands of MSPs learn through his YouTube channel, T-Minus365. Nick has been honored as a three-time Microsoft MVP for his educational content; his expertise and influence are the backbone of our mission, ensuring that you are in the best hands when it comes to security.
Nick joined Pax8 in 2017, where he would ultimately oversee product management for PSA and Microsoft integrations. Following his tenure at Pax8, Nick has continued to demonstrate his leadership prowess as an executive at various MSPs, culminating in his most recent role at Sourcepass.
Nick holds a Bachelor's Degree in Business Management from Florida State University, as well as a Minor Degree in Entrepreneurship. In his free time, Nick is an avid hiker, reader, and fitness-junkie.


