PowerShell Intune: Manage Devices with Microsoft Graph

PowerShell Intune: Manage Devices with Microsoft Graph

PowerShell Tips Editor 3 min read
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/managedDevices endpoint 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-AllPages helper function shown above follows @odata.nextLink until 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.

Send-Item -To