Review Stale Intune Devices Safely with Microsoft Graph PowerShell
A long gap since Intune last synced is a review signal, not proof that a device is retired. Travel, storage, connectivity problems, and enrollment changes can all make an active asset look stale. Start with a report and reconcile it against inventory, ownership, Autopilot registration, and your organization’s decommissioning process. Do not bulk-delete records solely because they crossed an age threshold.
Permissions and scope
The report reads managed-device records and the Autopilot inventory. It therefore needs DeviceManagementManagedDevices.Read.All and DeviceManagementServiceConfig.Read.All for the corresponding read operations. Deletion requires the more powerful DeviceManagementManagedDevices.ReadWrite.All; do not grant it to the reporting account. Microsoft Graph permissions and role requirements can vary by operation and auth type, so validate them against the operation’s current documentation before making changes.
Install-Module Microsoft.Graph.DeviceManagement -Scope CurrentUser
Connect-MgGraph -Scopes "DeviceManagementManagedDevices.Read.All","DeviceManagementServiceConfig.Read.All"
Create a review-only report
This example deliberately has no delete or disable branch. It excludes records without a last-sync timestamp (review those separately) and writes a CSV for human reconciliation. Fetch the Autopilot inventory before interpreting the report; serial-number matching is a useful flag, not an adequate identity proof by itself.
param(
[ValidateRange(30, 3650)]
[int]$InactiveDays = 90,
[string]$LogPath = ".\IntuneDeviceReview_$(Get-Date -Format yyyyMMdd-HHmmss).csv"
)
$cutoffDate = (Get-Date).AddDays(-$InactiveDays)
$devices = Get-MgDeviceManagementManagedDevice `
-Filter "operatingSystem eq 'Windows'" -All
$autopilotSerials = @(
Get-MgDeviceManagementWindowsAutopilotDeviceIdentity -All |
ForEach-Object { $_.SerialNumber.Trim().ToUpperInvariant() } |
Where-Object { $_ }
)
$review = $devices |
Where-Object { $_.LastSyncDateTime -and $_.LastSyncDateTime -lt $cutoffDate } |
ForEach-Object {
$serial = if ($_.SerialNumber) { $_.SerialNumber.Trim().ToUpperInvariant() } else { "" }
[PSCustomObject]@{
ManagedDeviceId = $_.Id
DeviceName = $_.DeviceName
SerialNumber = $_.SerialNumber
UserPrincipal = $_.UserPrincipalName
LastSync = $_.LastSyncDateTime
EnrolledDate = $_.EnrolledDateTime
ComplianceState = $_.ComplianceState
EntraDeviceId = $_.AzureAdDeviceId
AutopilotMatch = ($serial -and $serial -in $autopilotSerials)
ReviewStatus = "Needs human review; no action taken"
}
}
$review | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host "Review candidates: $($review.Count). Report: $LogPath"
Check that your Graph SDK version exposes the listed cmdlets, that you have authorization to read Autopilot inventory, and that the tenant’s data is complete. A serial match should trigger an exclusion/reconciliation check; it is not a reason to remove or retain an unrelated record without confirming device identity.
Before any approved cleanup
- Reconcile each candidate with the asset owner, inventory/CMDB, recent sign-in or endpoint-security telemetry, enrollment history, and Autopilot records.
- Confirm the device has been retired or otherwise approved for removal. Preserve the review CSV and ticket/approval reference.
- Identify the exact managed-device IDs. Re-run a narrowly scoped query immediately before action and compare the result to the approved list; never turn the report’s broad filter directly into a deletion loop.
- Use an appropriately privileged operator, least privilege, a small pilot, and the cmdlet’s
-WhatIf/confirmation controls where supported. Confirm the operation and its downstream effects in current Microsoft documentation.
Removing an Intune managed-device record is not the same as wiping or retiring the endpoint, and it can remove management/compliance signals used by other workflows. Deleting a corresponding Entra device object is a separate, higher-impact operation; do not automatically couple it to Intune cleanup. Validate identity, ownership, Conditional Access implications, approvals, and current Graph permissions independently. Disabling an Entra object can itself interrupt sign-in and access.