PowerShell Intune: Manage Devices with Microsoft Graph

Before a security review, you need a list of every device managed by Intune, their compliance status, and which ones are running outdated OS versions. Clicking through the Intune portal for 500 devices takes an hour; a PowerShell Intune script using the Microsoft Graph API delivers the same data in two minutes, formatted, filtered, and exported to CSV. This post covers authenticating to Graph, listing managed devices, checking compliance, filtering by OS, triggering remote sync, and building a device report.
Connect via Graph API
Intune management uses the same Microsoft Graph API as OneDrive and other M365 services. Your app registration needs the DeviceManagementManagedDevices.Read.All application permission (or ReadWrite.All for actions like sync):
$tenantId = "your-tenant-id"
$clientId = "your-client-id"
$clientSecret = "your-client-secret"
$tokenResponse = Invoke-RestMethod -Method POST `
-Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
-Body @{
client_id = $clientId
client_secret = $clientSecret
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
$token = $tokenResponse.access_token
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
Write-Host "Connected to Microsoft Graph"
List All Managed Devices
The Intune managed devices endpoint returns paginated results. Use the @odata.nextLink property to retrieve all pages:
function Get-AllPages {
param([string]$Uri, [hashtable]$Headers)
$results = @()
do {
$response = Invoke-RestMethod -Uri $Uri -Headers $Headers
$results += $response.value
$Uri = $response.'@odata.nextLink'
} while ($Uri)
$results
}
$devices = Get-AllPages -Uri "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices" `
-Headers $headers
Write-Host "Total managed devices: $($devices.Count)"
$devices | Select-Object deviceName, operatingSystem, osVersion, complianceState,
lastSyncDateTime, userDisplayName | Select-Object -First 5 | Format-Table -AutoSize
deviceName operatingSystem osVersion complianceState lastSyncDateTime
---------- --------------- --------- --------------- ----------------
LAPTOP-001 Windows 10.0.22000.1000 compliant 2026-05-03T14:22:11Z
PHONE-102 Android 13.0.0 noncompliant 2026-05-02T09:15:44Z
Check Device Compliance Status
Filter devices by their compliance state. complianceState values include compliant, noncompliant, conflict, error, inGracePeriod, and unknown:
$noncompliant = $devices | Where-Object complianceState -ne 'compliant'
Write-Host "Non-compliant / unknown devices: $($noncompliant.Count)"
$noncompliant |
Select-Object deviceName, userDisplayName, operatingSystem,
complianceState, lastSyncDateTime |
Sort-Object complianceState, deviceName |
Format-Table -AutoSize
Filter by OS Version
Identify devices running OS versions below a minimum threshold for patch compliance reporting:
$minWin10Build = "10.0.19045" # Windows 10 22H2
$minWin11Build = "10.0.22621" # Windows 11 22H2
$outdated = $devices | Where-Object {
$_.operatingSystem -eq 'Windows' -and
[version]$_.osVersion -lt [version]$minWin10Build
}
Write-Host "Windows devices below minimum OS version: $($outdated.Count)"
$outdated | Select-Object deviceName, osVersion, userDisplayName, lastSyncDateTime |
Sort-Object osVersion | Format-Table -AutoSize
Trigger Remote Sync
Send a sync request to a specific device to force it to check in with Intune immediately. Requires DeviceManagementManagedDevices.ReadWrite.All permission:
function Invoke-IntuneDeviceSync {
param([string]$DeviceId)
$syncUri = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/$DeviceId/syncDevice"
Invoke-RestMethod -Uri $syncUri -Method POST -Headers $headers
Write-Host "Sync request sent for device $DeviceId"
}
# Trigger sync on all non-compliant devices
foreach ($device in $noncompliant) {
Invoke-IntuneDeviceSync -DeviceId $device.id
Start-Sleep -Milliseconds 200 # Respect API rate limits
}
Export Device Report to CSV
Build a full device inventory CSV for compliance documentation or help desk reference:
$reportPath = "C:\Reports\Intune-Devices_$(Get-Date -Format 'yyyyMMdd').csv"
$devices | Select-Object deviceName, userDisplayName, userPrincipalName,
operatingSystem, osVersion, complianceState,
@{N='LastSync'; E={ $_.lastSyncDateTime }},
@{N='EnrollDate'; E={ $_.enrolledDateTime }},
managementAgent, deviceEnrollmentType, manufacturer, model |
Export-Csv -Path $reportPath -NoTypeInformation
Write-Host "Device report exported: $reportPath ($($devices.Count) records)"
Common Errors and Fixes
-
DeviceManagementManagedDevices.Read.All permission required. Requests to the
/deviceManagement/managedDevicesendpoint without this permission return a 403 Forbidden error. Add the permission in your app registration under API permissions, then click “Grant admin consent.” Application permissions (not delegated) are required for unattended scripts. -
Graph API paging needed for large device counts. The Graph API returns at most 100 results per page by default. The
Get-AllPageshelper function shown above follows@odata.nextLinkuntil all results are collected. Without it, you will silently miss devices if your estate has more than 100.
Related Cmdlets / See Also
Wrapping Up
Intune management via PowerShell and Microsoft Graph API gives you compliance reporting, bulk sync triggers, and OS version auditing without clicking through the portal. Always handle API paging with a nextLink loop, use application permissions with admin consent for unattended scripts, and export results to CSV for sharing with your security or compliance team.


