In certain occasions you may want to confirm what is the state of your devices or a subset of your devices in EntraID and cross reference this with the device status you see in Defender for Endpoint. Sometimes a device can appear as onboarded in Defender for Endpoint but given that an inactive device remains visible in the Defender portal for 180 days before it gets removed automatically, it can be challenging to understand what devices that show as inactive in the console are actually disabled in EntraID and no longer part of the device fleet you manage. The challenge today is that the EntraID API doesn’t expose the parameter “AccountState” to the Defender API, hence it is not possible to run an API call from the API explorer view from Defender portal to query this parameter, or view this information through Defender portal.
Proposed solution:
One way to achieve this result is by integrating few pieces of available technology. It sounds like a lot of moving parts but we do not need
- App registration in EntraID
- MS Graph API
- Defender API
- Powershell scripting language.
- File encryption
App registration in EntraID:
What this does is to provide a connection point between PowerShell and the information you want to access. Since creating an app registration creates a client secret and appId we can leverage these two pieces of information on the PowerShell script.
For reference from our documentation: How to register an application in EntraID.
1.- Jump into your EntraID tenant --> manage --> App registrations and click on “New registration” (Figure 1)
Figure 1. EntraID App registration process
2.- This is a simple app registration process, nothing complicated, it is just to obtain that AppID and ClientSecret value we need for the PowerShell script. For the purpose of the test, I called the app “DefenderEntraQueryApp” and configured with the following settings:
Authentication settings:
Fig 2. Authentication settings of app registration.
Certificates & Secrets:
Fig 3. Certificates and App client secret.
NOTE: remember when you create the app registration the client secret (Value) is shown only that time, after you move away from the app registration creation screen, the client secret will not be shown again. If you cannot grab the ClientSecret during registration of the app, you can click on the “New client secret” button from the view, create a new client secret and delete the previous one.
API Permissions:
Fig 4. API permissions.
After these parameters are configured in EntraID, you need to grab the following parameters from EntraID and insert this in the PowerShell script:
$tenantId = "MY-TENANT-ID"
$clientId = "MY-CLIENT-ID"
$secretPath = "C:\certs\clientSecret.dat"
$deviceList = Get-Content "C:\temp\devices.txt"
NOTE: The $deviceList variable is the text file where you will input the computer names you want to interrogate. Adjust the path for the text file to your preferred path and file name but be sure to reflect that in the script logic.
Encrypting client secret EntraID Application:
Since the client secret generated when registering the application in EntraID is in plain text, we cannot allow this information to be passed on in the script in plain text.
For this, we encrypt the client secret information into a .dat file using Windows DPAPI encryption and the script will pull it from a location on the user’s computer.
It is worth noting that the .dat file is bound to the user creating it, so, if you try to export this .dat file to another computer, the script will fail. Below is the one-time setup needed to create the .dat encrypted file the script will use.
To encrypt the client secret from your EntraID registered application, do the following:
The first line on the code sequence below adds the assemblies for System.Security that will instruct PowerShell this is an encryption operation. Run each of these lines, one by one in a PowerShell window with elevated privileges.
NOTE: change the path in line 6 of this piece of code to the path where you want the .dat file generated.
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)
Now that the app registration and client secret encryption is out of the way you can populate the text file with the list of computers you want to check, for example:
For the example, I am assuming the path for the text file is c:\temp
Fig 5. devices.txt file used as input to target multiple computers
Single entry per line, no space at the end. As you can see based on the device names from Fig 5. the script works for any supported OS as long as the machine is registered in EntraID.
After the text file is saved, open a Powershell windows with elevated privileges and proceed to connect your EntraID tenant for authentication purposes.
Use this command to connect to EntraID: Connect-AzureAD validate your credentials and them switch to the path where the script is, if you are not already there, and then execute the script in the PowerShell window:
Fig 6. Running the PS Script
Fig 7. Authenticating to EntraID:
Fig 8. Output of the script:
The script also exports the list to a .csv file, by default the path for the .csv file is c:\temp\DeviceStatus.csv
High level execution of the script:
PowerShell scripting code:
Code disclaimer:
The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.
NOTE: When using your scripting editing tool of choice, be aware of any additional spaces added as a result of the copy/past operation into your editing tool.
=== CONFIGURATION ===
#This script loops through a list of devices to check if the device is enabled or disabled in EntraID
#It uses the MS Graph API and a simple app registration in EntraID with consent granted to access
#Defender via Defender API
#Steps to define the pre-requisites for the script to run will be provided in a separate doc guide #Author: Edgar Parra - Microsoft v1.2
$tenantId = "MY-TENANT-ID"
$clientId = "MY-CLIENT-ID"
$secretPath = "C:\certs\clientSecret.dat"
$deviceList = Get-Content "C:\temp\devices.txt"
=== LOAD ENCRYPTED CLIENT SECRET ===
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 }
=== AUTHENTICATION ===
$body = @{ grant_type = "client_credentials" scope = "https://graph.microsoft.com/.default" client_id = $clientId client_secret = $clientSecret } try { $tokenResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $body $accessToken = $tokenResponse.access_token } catch { Write-Host "Error retrieving token: $($_.Exception.Message)" return } $headers = @{ Authorization = "Bearer $accessToken" "Content-Type" = "application/json" Accept = "application/json" }
=== LOOP THROUGH DEVICES ===
$results = @() foreach ($deviceName in $deviceList) { $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() | % { New-Object System.IO.StreamReader($) } | % { $_.ReadToEnd() } Write-Host "Error querying '$deviceName': $errorMessage" $results += [PSCustomObject]@{ DeviceName = $deviceName Status = "Error" } } }
=== EXPORT RESULTS TO CSV ===
$results | Export-Csv -Path "C:\temp\DeviceStatus.csv" -NoTypeInformation
Explore additional resources:
For further insights and guidance on data protection encryption and app registrations in EntraID, consider reviewing these related articles: