We often face a big problem managing devices: Microsoft Defender for Endpoint (MDE) keeps records of old, inactive devices for up to six months. This makes it impossible to know if a device is just sleeping or if it has been turned off forever (disabled) in Microsoft Entra ID.
This confusion wastes security licenses and clutters your inventory.
Microsoft now has a solution for this using a PowerShell script. This guide shows you how to leverage the Microsoft Graph API to quickly check a list of devices against Entra ID and find out which ones are truly disabled.
Prerequisites
To build this solution, you need these components for automation and security:
- Entra ID App Registration: A non-interactive service principal for Graph API authentication.
- API Permissions: The App Registration requires the
Device.Read.Allpermission. - Encrypted Client Secret: The App Registration secret must be secured using Windows DPAPI encryption (a mandatory security best practice).
1) App Registration in Entra ID
- Go to the Microsoft Entra admin center ->App registrations.
- Select New registration and give your app a clear name (for example, MDE_Cleanup_Script), then click Register.
- Once the app is created, go to its Overview page and note down the following details for later use in the script:
-
Application (client) ID →
$clientId -
Directory (tenant) ID →
$tenantId
-
2) Configure the App
From the left navigation panel, configure the following:
- Authentication
-
-
Select Authentication and review the default settings.
-
2. Certificates & Secrets
-
-
Go to Certificates & secrets → New client secret.
-
Add a description and select an expiry period.
-
Click Add, then copy the Value immediately — it will not be visible again.
-
-
API Permissions
-
Go to API permissions → Add a permission → Microsoft Graph → Application permissions.
-
Select Device.Read.All.
-
Click Grant admin consent to activate the permission.
-
3) Encrypt Your Client Secret
It is a security risk to store passwords in a text file. We will use a Windows feature called DPAPI to encrypt the secret. This file only works on the computer and for the user who encrypted it. Run the following script in a PowerShell window.
Add-Type -AssemblyName System.Security
$plainText = "your-client-secret"
$secureBytes = [System.Text.Encoding]::UTF8.GetBytes($plainText)
$encrypted = [System.Security.Cryptography.ProtectedData]::Protect(
$secureBytes, $null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser
)
[System.IO.File]::WriteAllBytes("C:\temp\clientSecret.dat", $encrypted)
This creates a secure file at C:\temp\clientSecret.dat.
Your main script will use this to authenticate safely.
4) Prepare Your Device List
Create a text file named devices.txt at C:\temp\ with one device name per line:
LAPTOP-001
DESKTOP-002
TAB-003
5) Save the PowerShell Script
Save the following script as,C:\temp\Get-EntraDeviceStatus.ps1
Note: You need to replace $tenantId, $clientId, and $secretPath with your specific Entra ID Tenant ID, Application ID, and the local path to your encrypted client secret file, respectively. Ensure the file designated by $deviceListPath exists and holds the device names you want to check.
$tenantId = "MY-TENANT-ID"
$clientId = "MY-CLIENT-ID"
$secretPath = "C:\temp\clientSecret.dat"
$deviceListPath = "C:\temp\devices.txt"
$outputPath = "C:\temp\DeviceStatus.csv"
if (-not (Test-Path $deviceListPath)) {
Write-Error "Device list file not found at $deviceListPath. Script will exit."
return
}
$deviceList = Get-Content $deviceListPath
Add-Type -AssemblyName System.Security
if (-not (Test-Path $secretPath)) {
Write-Host "Encrypted client secret file not found at $secretPath."
return
}
try {
$encryptedSecret = [System.IO.File]::ReadAllBytes($secretPath)
$decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(
$encryptedSecret,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser
)
$clientSecret = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
}
catch {
Write-Host "Error decrypting client secret: $($_.Exception.Message)"
return
}
$authUri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$body = @{
grant_type = "client_credentials"
scope = "https://graph.microsoft.com/.default"
client_id = $clientId
client_secret = $clientSecret
}
try {
$tokenResponse = Invoke-RestMethod -Method Post -Uri $authUri -Body $body
$accessToken = $tokenResponse.access_token
}
catch {
Write-Host "Error retrieving token. Check tenant ID, client ID, and secret: $($_.Exception.Message)"
return
}
$headers = @{
Authorization = "Bearer $accessToken"
"Content-Type" = "application/json"
Accept = "application/json"
}
$results = @()
foreach ($deviceName in $deviceList) {
$deviceName = $deviceName.Trim()
if (-not $deviceName) { continue }
$escapedDeviceName = $deviceName -replace "'", "''"
$uri = "https://graph.microsoft.com/v1.0/devices?`$filter=displayName eq '$escapedDeviceName'"
try {
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
if ($response.value.Count -eq 0) {
Write-Host "Device '$deviceName' not found."
$results += [PSCustomObject]@{ DeviceName = $deviceName; Status = "Not Found" }
}
else {
$device = $response.value[0]
$status = if ($device.accountEnabled) { "Enabled" } else { "Disabled" }
Write-Host "$($device.displayName): $status"
$results += [PSCustomObject]@{ DeviceName = $device.displayName; Status = $status }
}
}
catch {
$errorMessage = $_.Exception.Response.GetResponseStream() | ForEach-Object {
(New-Object System.IO.StreamReader($_)).ReadToEnd()
}
Write-Host "Error querying '$deviceName': $errorMessage"
$results += [PSCustomObject]@{ DeviceName = $deviceName; Status = "Error" }
}
}
if ($results.Count -gt 0) {
$results | Export-Csv -Path $outputPath -NoTypeInformation
}
6) Run the Script
Optionally, connect to Azure AD using Connect-AzureAD if you want to cross-check or run additional manual verification commands. Then, run the script.
.\Get-EntraDeviceStatus.ps1
The script will produce the output as shown below:
If the CSV shows a device as Disabled, you have confirmation. That machine is officially decommissioned in Entra ID. You can now confidently retire (delete) that old record in the MDE portal, clean up your inventory, and free up security licenses.
Cleaning up inactive MDE devices no longer needs to be a guessing game. With this Graph API–powered PowerShell script, you can instantly verify each device’s Entra ID status and make confident cleanup decisions. This small automation helps you save security licenses, maintain a lean device inventory, and ensure your MDE data reflects reality.


