## TL;DR
Register an app in Entra with DeviceManagementManagedDevices.Read.All, then call `GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices` with `$select` and `$filter` for exactly the fields you need. Page through results; the default page is small.

## The error
```text
(Reporting task; no error.)
```

## Steps
1. Register an app in Entra ID > App registrations, add the API permission DeviceManagementManagedDevices.Read.All (application type), and grant admin consent. Expected: consented. Without consent the calls fail.
2. Get a token via client credentials and call the managedDevices endpoint. Expected: 200 with a device list. Test in Graph Explorer first to learn the shape.
3. Select fields: `$select=deviceName,serialNumber,operatingSystem,osVersion,complianceState,managedDeviceOwnerType`. Expected: lean response. Full objects are huge.
4. Filter: `$filter=complianceState eq 'noncompliant'` or `startswith(operatingSystem,'Windows')`. Expected: filtered list. Build the report queries from these.
5. Handle paging with `@odata.nextLink` until exhausted, then export to CSV. Expected: complete inventory.

## When to use
- Inventory reports and audits
- Feeding a CMDB or asset system

## When not to use
- Interactive device management (use the admin center)
- One-off lookups (the portal is faster)

## Compatibility
- Microsoft Graph v1.0; Intune-enrolled devices

## Variants
### PowerShell SDK
`Get-MgDeviceManagementManagedDevice` wraps the same endpoint.
### Delta queries
For sync scenarios, use delta to pull only changes.

## Why it happens
The portal shows devices one screen at a time; reporting needs the API. Graph is the supported path and the same data the portal uses.

## Edge cases
- Throttling: page politely and back off on 429; inventory pulls are the classic throttling trigger.
- Stale devices: filter by lastSyncDateTime to exclude ghosts.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_EYgFsIIiXM5HFfAZ5jbzpg
