Last Updated on February 20, 2026 by Oktay Sari
āThe same kind of script is sorely needed for Defender. It uses hostname as the unique identifier for Macs so any device name change creates a new record and uses up a license. Itās bonkers.ā
That was Luke on LinkedIn, commenting on my Entra ID duplicate scanner. And he was absolutely right. MDEās device inventory can feel like a ghost town of orphaned records.
So there I was, staring at the device inventory in security.microsoft.com, counting duplicate device records like sheep before bed. Except these sheep werenāt helping me sleep. They were keeping me up at night. Before I knew it, it was 3 AM for the next couple of days, and another PowerShell script was born. My wife thanks you too, Luke. š¤
This project won’t completely solve Luke’s problem. You still can’t delete device records from Microsoft Defender, and the 180-day retention window is non-negotiable. But what it does do is give you visibility into the mess, automatically identify which records are the ghosts, tag them so they’re easy to filter out, and optionally exclude them from your vulnerability management reports.
TL;DR: MDE creates duplicate device records every time a device is renamed, reimaged, or re-onboarded, and you can’t delete them. This PowerShell script groups devices by HardwareUuid, scores each record across 21 signals to identify orphans, and tags them via the official API. Optionally, it generates a helper script that can bulk-exclude flagged devices from Vulnerability Management using undocumented portal APIs (proof of concept, requires XDRInternals). GitHub link
Table of Contents
- A Quick Note on Licensing
- What This Project Actually Is (and Isnāt)
- Why Duplicate Device Records Pile Up in MD
- HardwareUuid: The Stable Anchor in MD
- How the Script Finds Duplicate Device Records
- Scoring Duplicate Device Records: 21 Signals Across 3 Tiers
- Two Modes for Handling Duplicate Device Records
- Excluding Duplicate Device Records: The Undocumented API Rabbit Hole
- Credits: Standing on the Shoulders of Giants
- Requirements and Setup
- Running the Duplicate Device Records Scanner
- Security and Credential Handling
- Disclaimers and Warnings
- Resources
A Quick Note on Licensing
Before we dive in, letās address the licensing elephant in the room. Luke mentioned duplicate records āusing up a license.ā Now technically, MDE licensing is per user, not per device (up to five devices per user). The license usage report is calculated based on users who signed in to onboarded devices, not the number of device records in the inventory. So duplicate ghost records that nobody signs into shouldnāt directly eat a license.
However, they do clutter your inventory, inflate device counts, and make license usage reports harder to interpret. And letās be honest, nobody wants to explain to their CISO why the portal shows 347 devices when you only manage 200. š
What This Project Actually Is (and Isnāt)
I want to be upfront about something. This project has two very different halves.
The scanner and tagging workflow? Thatās built on solid foundations. It uses official, documented Microsoft APIs with proper OAuth authentication. The building blocks are all there for anyone who wants to integrate this into their automation workflows or adapt it for scheduled runs.
Jordan, If you’re reading this…You’re the one who put the idea in my head to slap a proper cowboy hat banner on this thing. Much obliged, partner! #DutchCowboy š¤
The MDVM exclusion helper? Thatās a proof of concept. And I want to be honest about what that means in practice.
You need to manually capture browser cookies, those cookies expire, the APIs are undocumented and could change without notice, and the whole setup requires XDRInternals plus a working understanding of portal authentication. Itās not exactly ārun it and forget itā automation. Itās more like ārun it when you need it and hope Microsoft didnāt change the endpoints since last Tuesday.ā
I built this because the capability to programmatically exclude duplicate device records simply didnāt exist anywhere else, and I wanted to prove it could be done. But Iām one person with a PowerShell window and a stubbornness problem.
I’ll spare you the cookie-capturing tutorial. If you’re brave enough to run scripts against undocumented APIs, I trust you know how to open DevTools and grab a session token.
A Call to the Community
If youāre reading this and thinking āI could make this better,ā please do. Seriously. Whether thatās building a more robust authentication flow, wrapping this in a proper module, creating a GUI, or even convincing Microsoft to add exclusion to the public API (looking at you, product team š), Iād love to see it.
The scanner script, the exclusion research, and all the API documentation are on GitHub Fork it, break it, rebuild it, make it better. If this proof of concept inspires someone smarter than me to build something that actually automates the full lifecycle, then this whole project was worth every late night.
The best tools in this community have always been built together. š„·
Why Duplicate Device Records Pile Up in MDE
Microsoft Defender identifies devices by a generated 40-character hex ID. So far so good. However, every time a deviceās hostname changes, gets reimaged, or goes through an offboard/re-onboard cycle, MDE creates a brand new device record. The old one just⦠sticks around.
MDE does have built-in deduplication, and itās on by default. However, it doesnāt catch everything. Those missed duplicates clutter up your device inventory, skew your device counts, and make it harder to get an accurate picture of your security posture.
The kicker? There is no way to delete a device record from Defender. And honestly, thereās a good reason for that. If admins could permanently remove devices, so could an attacker who gains a foothold in your environment. Wipe all device records, cover your tracks, and nobody can verify what happened. The forensic trail would be gone. So Microsoft made device records immutable by design. They go inactive after 7 days, drop out of exposure scores after 30 days, and finally disappear after 180 days.
Fair enough. But hereās where it gets painful. For Windows devices, you can at least offboard them via the API to stop telemetry and start the countdown. For macOS, iOS, Android, and Linux? The offboard API endpoint simply doesnāt support those platforms. You can exclude devices from Vulnerability Management in the portal (even in bulk), but thereās no way to import a CSV, or call an API. Nothing ruins your Monday morning coffee quite like manually ticking checkboxes on 47 duplicate device records scattered across pages of inventory. ā
For macOS specifically, thereās an additional challenge. Serial numbers are often empty in Advanced Hunting queries. Therefore, the approach I used for the Entra ID duplicate scanner (serial number as the stable anchor) doesnāt work here. I needed a different identifier.
HardwareUuid: The Stable Anchor in MDE
After digging through the MDE REST API and Advanced Hunting schemas, I found HardwareUuid. This is a unique hardware identifier that stays constant regardless of hostname changes, reimaging, or re-onboarding. Itās the key to reliably grouping duplicate device records in Microsoft Defender for Endpoint, and the equivalent of what serial numbers are for Entra ID: the fingerprint of the physical device.
The catch? HardwareUuid is only available through Advanced Hunting KQL queries. You cannot get it from the REST API alone. Additionally, Advanced Hunting has a 30-day lookback window, so very stale devices may not have HardwareUuid data available. The script handles this gracefully by falling back to a staleness-only analysis for those āunresolvedā devices.
For mobile devices (iOS and Android), HardwareUuid is often empty too. In those cases, the script pulls the IMEI from Intune via Microsoft Graph API and uses that as the fallback grouping identifier. Because when the herd is scattered, you use every tool in the saddlebag. š¤
Hereās the KQL query the script runs under the hood to grab HardwareUuid:
DeviceInfo
| where OSPlatform in ("macOS", "MacOsX")
| summarize arg_max(Timestamp, *) by DeviceId
| extend HardwareUuid = coalesce(
column_ifexists("HardwareUuid", ""),
tostring(parse_json(AdditionalFields).HardwareUuid)
)
| project DeviceId, DeviceName, HardwareUuid, MergedToDeviceId, MergedDeviceIds, Model
The coalesce + column_ifexists pattern handles the fact that some tenants expose HardwareUuid as a direct column while others still have it nested in the AdditionalFields JSON. Because consistency is apparently optional. š
How the Script Finds Duplicate Device Records
The detection pipeline for finding duplicate device records works in multiple stages. Let me walk you through the flow:
Step 1: Authenticate with OAuth 2.0
The script supports both client secret and certificate-based authentication. Certificate auth uses a JWT client assertion with RS256 signing, so no external modules are needed. In guided setup mode, secrets and passwords are read from the clipboard. Nothing appears on screen.
Step 2: Pull All Devices from the REST API
The script calls GET /api/machines with platform filtering and handles pagination. MDE returns up to 10,000 records per page, and the script follows @odata.nextLink until all devices are collected.
Step 3: Run Advanced Hunting for HardwareUuid
A KQL query pulls HardwareUuid, MergedToDeviceId, MergedDeviceIds, and Model data. A second KQL query pulls DeviceLogonEvents for recent user activity per device.
Step 4: Cross-reference Intune (Automatic)
If Microsoft Graph permissions are available, the script automatically pulls Intune managed device data. It matches MDE devices to Intune by AadDeviceId, adding enrollment status, compliance state, last sync date, and IMEI. If Graph permissions arenāt available, this step is skipped gracefully. No errors, no drama.
Step 5: Correlate and Group
REST API data, Advanced Hunting results, logon activity, and Intune data are merged into enriched device records. Devices are then grouped by HardwareUuid (or IMEI for mobile) to identify duplicate groups.
Step 6: Score Each Record
This is where the magic happens. Every record in a duplicate group gets scored across 21 heuristic signals.
Scoring Duplicate Device Records: 21 Signals Across 3 Tiers
Iām a firm believer that āstale = deleteā is too simplistic when dealing with duplicate device records. A device might be inactive for 10 days because the user is on vacation. Thatās not the same as a device that hasnāt checked in for 120 days, has no sensor data, no Entra registration, no logon activity, and its sibling is actively reporting. The scoring engine evaluates 21 signals organized across three tiers. Overkill again?? Probablyā¦
Tier 1: MDE REST API Signals (Always Active)
These signals come from data thatās always available, no Advanced Hunting or Graph required:
| Score | Signal |
|---|---|
| +1 to +4 | Graduated inactivity: +1 (8-14 days), +2 (15 to threshold), +3 (threshold to 3x), +4 (beyond 3x threshold) |
| +3 | MergedToDeviceId present: MDE already absorbed this record into another |
| +3 | Confirmed ghost: group leader active within 7 days, this record has >7 day gap |
| +2 | OnboardingStatus = InsufficientInfo |
| +2 | HealthStatus = NoSensorData or ImpairedCommunication |
| +2 | No AadDeviceId: no Entra registration link |
| +1 | Older firstSeen: not the newest record in the group |
| +1 | No tags while sibling has tags |
| +1 | Same AadDeviceId as group leader: ghost shares Entra identity |
| +1 | Oldest agentVersion in group: stale sensor build |
| +1 | DefenderAvStatus = notUpdated or disabled |
| -2 | Has MergedDeviceIds: this is the survivor record |
| -1 | Health: Active |
| -1 | Most recent lastSeen in group |
| -1 | Newest agentVersion in group |
The graduated inactivity scoring is worth calling out. In v1.0, any device inactive for more than 7 days scored +3 immediately. That caught too many false positives. Someone goes on a two-week holiday and suddenly their Mac gets flagged as an orphan? Not cool. The graduated approach gives lower scores for shorter absences and reserves the highest penalty for truly abandoned devices.
Tier 2: Advanced Hunting Logon Activity (Automatic)
| Score | Signal |
|---|---|
| +2 | No logon events in last 30 days while sibling has logon activity |
| -1 | Has recent logon activity |
This is powerful. A device might still appear āactiveā in MDE health status, but if nobody has actually logged on to it in 30 days while its sibling has active logon events, thatās a strong signal.
Tier 3: Intune Cross-Reference (Automatic, Opt-Out with -SkipIntune)
| Score | Signal |
|---|---|
| +2 | No Intune enrollment while sibling is enrolled |
| +1 | Intune enrolled but non-compliant |
| -2 | Active Intune enrollment with recent sync (within 30 days) |
Intune data adds the MDM layer. A device thatās enrolled, compliant, and recently synced gets a significant negative score (protection from being flagged). A device with no Intune enrollment at all while its sibling is enrolled? Thatās a strong orphan signal.
How Scores Become Recommendations
After scoring, each device gets a recommendation:
- TAG (High confidence): OrphanScore >= 5. Safe to tag and exclude.
- REVIEW (Moderate): OrphanScore >= 3. Probably an orphan, but human eyes recommended.
- REVIEW (Low): OrphanScore < 3. Low confidence, proceed with caution.
- KEEP (Primary): Lowest score in the group. This is your real device.
Pro tip: The score range across all 21 signals is -8 to +27. A device scoring 12+ is about as dead as a doornail. A device scoring 2? Thatās probably just someone on PTO. Trust the scoring but always review the CSV!
Duplicate Device Records Scanner Output
The scanner generates several CSV reports:
| File | Contents |
|---|---|
| all_mde_{platform}_devices.csv | All devices with full analysis detail |
| duplicate_{platform}_records.csv | Only duplicate groups (2+ records per HardwareUuid) |
| tag_{platform}_recommendations.csv | Flagged records with scores, reasons, and exclusion advice |
| unresolved_{platform}_devices.csv | Devices without HardwareUuid (staleness-only analysis) |
| Invoke-ExcludeDevices.ps1 | Generated exclusion helper (when -GenerateExclusionScript is used) |
| scan_transcript.log | Full session transcript for audit trail |
Two Modes for Handling Duplicate Device Records
The script has two modes of operation for handling duplicate device records, and this distinction is important.
The Manual Way (Portal UI)
Before we get into automation, letās look at what Microsoft actually supports today. If you want to exclude a device from Vulnerability Management, hereās the official process:
- Go to security.microsoft.com ā Assets ā Devices
- Find the device you want to exclude and click on it
- Select the three dots menu (ā¦) and choose Exclude device
- Pick a justification from the dropdown (compromised, test/lab, or other)
- Add optional notes and confirm
To be fair, you can select multiple devices in the inventory and bulk-exclude them from the actions bar. So it’s not strictly one at a time. But there’s no CSV import and no API call. For a handful of devices, the portal works fine. For fifty scattered across pages of inventory? You’re still manually ticking checkboxes. For hundreds? That’s when you start looking at the automation options below. ā For the full walkthrough, check the official Microsoft documentation.
Mode 1: Scan and Tag (Safe, Read-Write via Official API)
# Scan and tag high-confidence orphans
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" -AppSecret "<secret>" `
-TagStaleDevices
This mode uses the official MDE REST API to tag duplicate device records with a configurable tag (default: StaleOrphan_macOS, StaleOrphan_Windows, etc.). Tagging is non-destructive. It adds a label to the device in the portal that you can filter on. Nothing gets deleted, excluded, or offboarded. You can even run it with -WhatIf first to see what would be tagged without making any changes.
This is the āsleep well at nightā option. š“
About API tags… MDE supports multiple ways to tag devices: manually through the portal, via the REST API, through a registry key (HKLM\...\DeviceTagging\Group) deployed by GPO or SCCM, via an Intune custom OMA-URI profile, through config profiles for macOS and Linux, or dynamically using Asset Rule Management in Defender XDR.
The script uses the API method (POST /api/machines/{id}/tags) because it writes the tag server-side directly on the cloud record. That matters here because stale and orphaned devices arenāt checking in anymore. They would never pick up a registry change, an Intune profile, or a config profile update. The API is the only method that can reach devices that have already gone dark.
Mode 2: Scan, Tag, and Generate Exclusion Script (Uses Undocumented APIs)
# Scan, tag, AND generate the exclusion helper
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" -AppSecret "<secret>" `
-TagStaleDevices -GenerateExclusionScript
When you add the -GenerateExclusionScript flag, the scanner generates a self-contained helper script called Invoke-ExcludeDevices.ps1. This helper script can auto bulk-exclude flagged devices from Microsoft Defender Vulnerability Management (MDVM) by calling undocumented portal APIs.
This is where things get spicy. š¶ļø
Excluding Duplicate Device Records: The Undocumented API Rabbit Hole
Hereās the thing. Microsoft provides no official REST API or Graph API endpoint for excluding duplicate device records from Vulnerability Management. As of February 2026, the only supported method is clicking through the UI at security.microsoft.com. For organizations managing hundreds or thousands of devices, thatās not exactly scalable.
So I did what any self-respecting cowboy would do. I opened the browser DevTools, clicked the āExcludeā button in the portal, and captured the network request. Two API endpoints emerged from the browser traffic:
POST /apiproxy/mtp/k8s/machines/UpdateExclusionState
GET /apiproxy/mtp/ndr/machines/{id}/exclusionDetails
The POST endpoint accepts a JSON body with the device IDs, exclusion state, justification, and notes. The GET endpoint returns the current exclusion details for a device.
The Authentication Challenge
So why canāt we just reuse the same OAuth token from the main scanner? It comes down to how Microsoft architected these two surfaces.
The official MDEAPI (api.security.microsoft.com) is a public REST API designed for programmatic access. You register an app in Entra ID, grant it permissions, and authenticate with OAuth 2.0 client credentials. Standard stuff.
The exclusion endpoints (security.microsoft.com/apiproxy/mtp/…) are a completely different story. Microsoft provides no public API for device exclusion. The only documented method is through the portal UI. When you click the āExcludeā button in the Defender portal, your browser calls these internal endpoints behind the scenes. They were never designed for programmatic access. They expect a logged-in userās browser session, complete with:
- sccauth cookie (portal session token)
- XSRF-TOKEN cookie + x-xsrf-token header (CSRF protection)
In other words, these APIs only trust āIām a human clicking buttons in a browser,ā not āIām a service principal with app permissions.ā Thatās why we need a completely separate authentication flow, and why XDRInternals exists: it bridges the gap between scripting and the portal by replaying browser session cookies programmatically.
So What Are These Cookies, Exactly?
After spending way too many hours reading about browser cookies and authentication flows, hereās how I think this works in practice:
Itās essentially a two-step trust chain:
- ESTSAUTHPERSISTENT is your proof that you have a valid Entra ID browser session. Youāve passed MFA, Conditional Access, all the checks. Microsoft documents these as browser session cookies used for SSO.
- sccauth is your proof that you have a valid Defender portal session tied to a specific tenant. Microsoft doesnāt publicly document this one (of course), but itās what makes the portalās internal API endpoints trust your requests.
XDRInternals bridges these two worlds. Its Connect-XdrByEstsCookie cmdlet takes your ESTSAUTHPERSISTENT cookie, hits the security portalās sign-in flow, and exchanges that Entra session for portal session cookies. Thatās the āOAuth redirect danceā happening under the hood.
This is the part where I lost track of time and my coffee went cold; On top of the session cookies, the portal also uses something called a āDouble Submit Cookieā pattern for CSRF protection. Basically: the server sets an XSRF-TOKEN cookie when you log in, and every write request (POST, PUT, PATCH) must send that same value back in an x-xsrf-token header. If the header is missing or doesnāt match the cookie, you get a 400 Bad Request.
While I was completely geeking out over the portal traffic in DevTools, I stumbled onto something that will save you a few hours of debugging: the XSRF-TOKEN cookie value is URL-encoded, but the x-xsrf-token header expects the decoded version. That means you need a [System.Web.HttpUtility]::UrlDecode() step in your script. Skip it, and you’ll get a cryptic ‘incorrectly loaded page’ error even though your session cookies are perfectly valid. Ask me how I know. š
Credits: Standing on the Shoulders of Giants
I want to give major credit to Fabian Bader and Nathan McNulty for building XDRInternals. Without their work, the exclusion helper for duplicate device records wouldnāt exist.
XDRInternals is a community-driven PowerShell module that provides direct access to the Microsoft Defender XDR portal APIs. It solves the authentication problem by taking an ESTSAUTHPERSISTENT cookie from login.microsoftonline.com, performing the OAuth redirect dance to the security portal, and retrieving the session cookies needed for portal API calls. It even auto-refreshes the XSRF token every 5 minutes.
I actually saw Fabian present XDRInternals live at Workplace Ninja Connect 2026 in the Netherlands just a few weeks ago (February 4, 2026, at the Van der Valk Hotel in Gorinchem). Seeing the module in action during his session is what pushed me over the edge from āI should probably build that exclusion thingā to āIām building it this weekend.ā The community at Workplace Ninja events is something special. If youāve never attended, youāre missing out. š„·
The XDRInternals module also includes a browser extension called XDRay (available for Edge, Chrome and Firefox) that can automatically capture portal API calls and translate them to Invoke-XdrRestMethod calls. Psssst…This is actually the easiest way to get the sccauth and XSRF tokens for the exclusion helper script.
How the Exclusion Helper Uses XDRInternals
The exclusion helper doesn’t talk to the Defender portal directly. It uses XDRInternals as the authentication layer between your browser session and the undocumented portal APIs. In short: you provide a session cookie, XDRInternals establishes a valid portal connection, and the script uses that connection to call the internal exclusion endpoints (/apiproxy/mtp/k8s/machines/UpdateExclusionState and /apiproxy/mtp/ndr/machines/{id}/exclusionDetails).
The script offers two authentication methods:
- ESTSAUTHPERSISTENT cookie: Captured from the browser login flow. XDRInternals handles the rest.
- sccauth + XSRF tokens (default): Captured directly from the portal using DevTools or the XDRay extension.
I spent a lot of time trying to get the ESTSAUTHPERSISTENT flow working reliably, and I kept running into issues. The cookie-to-portal-session exchange is sensitive to timing, Conditional Access policies, and session state in ways that were hard to debug consistently. In the end, I fell back to capturing sccauth + XSRF tokens directly from the portal, which just works. Thatās why itās the default method. If you have better luck with ESTSAUTHPERSISTENT, more power to you. Let me know what Iām doing wrong. š (Fabian??, Nathan??)
Both methods read cookie values from the clipboard with no-echo keypress confirmation. Nothing gets displayed on screen.
Requirements and Setup
For the Main Scanner
- PowerShell 7+
- An Azure App Registration with WindowsDefenderATP permissions:
- Machine.Read.All (read device records)
- AdvancedQuery.Read.All (Advanced Hunting for HardwareUuid + logon activity)
- Machine.ReadWrite.All (optional, for tagging)
- Microsoft Graph permission (for Intune cross-reference, automatic):
- DeviceManagementManagedDevices.Read.All
- Microsoft Defender for Endpoint P1 or P2 license
Automated App Registration Setup
Donāt want to create the app registration manually? Use the included helper script:
# Create app registration with all permissions
./New-MdeAppRegistration.ps1 -IncludeWritePermission -IncludeIntunePermission
This automates the entire setup process: creates the app, adds the required API permissions, generates a client secret, and grants admin consent. Save the AppSecret immediately, because you wonāt be able to retrieve it again.
For the Exclusion Helper (Optional)
- XDRInternals module (Install-Module XDRInternals -Scope CurrentUser)
- Access to the security.microsoft.com portal
- A browser to capture session cookies
Running the Duplicate Device Records Scanner
Interactive Guided Setup
If you run the script with no parameters at all, it walks you through everything interactively. Platform selection, authentication method, Intune cross-reference, tagging, exclusion script generation. Everything.
./Find-DuplicateDefenderDevices.ps1
Now confirm the devices have been tagged and check the CSV files. If you’d rather grab the reins yourself…
Basic Scan (macOS, Default)
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" -AppSecret "<secret>"
Scan Windows Devices
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" -AppSecret "<secret>" `
-Platform Windows
Scan All Platforms
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" -AppSecret "<secret>" `
-Platform All
Certificate Authentication
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" `
-CertificatePath "cert.pfx" -CertificatePassword "pass"
Full Workflow: Scan, Tag, and Generate Exclusion Script
./Find-DuplicateDefenderDevices.ps1 `
-TenantId "<id>" -AppId "<id>" -AppSecret "<secret>" `
-TagStaleDevices -GenerateExclusionScript
Run the Generated Exclusion Script
Once the scanner finishes with -GenerateExclusionScript, it drops an Invoke-ExcludeDevices.ps1 file right next to your CSV reports. This is the second half of the workflow: taking those tagged, high-confidence orphans and excluding them from Vulnerability Management.
Before you run it, you’ll need one thing the scanner didn’t need: a browser session. Remember, this script talks to undocumented portal APIs, not the official REST API. That means it authenticates through your browser’s session cookies using XDRInternals, not through an app registration.
# Exclude devices with OrphanScore >= 5 (default)
./Invoke-ExcludeDevices.ps1
The first thing you’ll see when you run it is a wall of warnings. And I mean a wall. That’s intentional…
This script talks to undocumented portal APIs, not the official REST API. It requires browser session cookies for authentication and comes with zero guarantees from Microsoft. You have to type YES in full to continue. No fat-fingering your way past this one.
After accepting the risk, the script loads the CSV generated by the scanner and filters devices based on their OrphanScore. By default, it only picks up high-confidence orphans (score >= 5). You’ll see exactly which devices were selected before anything happens.
Next up: authentication. The script gives you two options:
- ESTSAUTHPERSISTENT cookie (recommended by XDRInternals): Your Entra ID browser session cookie. The module uses this to perform the OAuth redirect dance and obtain portal access.
- Manual sccauth + XSRF tokens: If the ESTS cookie method gives you trouble, you can grab the
sccauthandXSRF-TOKENvalues directly from your browser’s DevTools or the XDRay extension.
I used option 2: sccauth + XSRF tokens.. Go grab the sccauth and XSRF-TOKEN
Once authenticated, the script runs a pre-flight check on every device: are they already excluded? Still reachable? It shows you a summary with the justification and notes, then asks for explicit confirmation before proceeding. Nothing happens without your green light.
After exclusion completes, you get a detailed report showing exactly what happened to each device. The script also reminds you that it can take up to 10 hours for exclusions to fully reflect in MDVM views. And notice that last line: “Close this PowerShell window for maximum security.” Those session cookies are powerful. Treat them accordingly.
The -Action GetStatus option is your “trust but verify” move. Run it after excluding to confirm the portal actually processed your requests. Because with undocumented APIs, you always double-check.
# Check exclusion status without making changes
./Invoke-ExcludeDevices.ps1 -Action GetStatus
Security and Credential Handling
Security matters. Especially when youāre building tools that interact with security APIs to manage duplicate device records. Hereās what the script does to protect your credentials:
Main scanner: – Client secrets and .pfx passwords are read from the clipboard in guided setup mode. Nothing appears on screen. – The finally block scrubs AppSecret, CertificatePassword, OAuth tokens, and Graph tokens from memory on exit (normal or interrupted). – Transcript logging starts after authentication so no sensitive data gets logged.
Exclusion helper: – Cookie values are read via clipboard with no-echo keypress confirmation. – On exit, the script clears cookie variables, clipboard contents, PSReadLine command history, and forces garbage collection. – A runtime risk acceptance gate (type āYESā to proceed) ensures you know what youāre about to do.
Disclaimers and Warnings
Alright, serious hat on for a moment. š¤ ā”ļøš©
The main scanner (Find-DuplicateDefenderDevices.ps1) uses official, documented MDE REST APIs and Advanced Hunting to find duplicate device records. Scanning is read-only by default. The -TagStaleDevices switch adds tags but does NOT delete or offboard devices. This is as safe as it gets.
And please, donāt just trust any script you find out there (including mine). Read the code. Understand what it does. Review the CSV reports before taking action. The script provides recommendations, but the final decision is always yours.
I’m just a guy with a terminal and too much coffee. No guarantees, no warranties, no SLA. The code is open. Read it, review it, and decide for yourself if it meets your bar. This script is provided āAS ISā, without warranty of any kind, express or implied. You are solely responsible for any actions taken using this script. USE AT YOUR OWN RISK.Ā
Whatās Next?
The 180-day retention cycle is still the only way to truly remove duplicate device records from MDE. In a perfect world, Microsoft would give us an official API for device exclusion. Until then, this script helps you take back control of your device inventory.
If you find issues, have feature requests, or want to contribute, the script is on GitHub. Pull requests are welcome.
And if youāre attending any upcoming community events, come say hi. Iāll be the one in the cowboy hat. š¤
Resources
- Find-DuplicateDefenderDevices on GitHub
- XDRInternals by Fabian Bader and Nathan McNulty
- XDRay Browser Extension
- My previous post: Duplicate macOS Device Records in Entra ID
- Microsoft Learn: Exclude devices from Vulnerability Management
- Microsoft Learn: Create and manage device tags
- Microsoft Learn: MD API documentation
- Microsoft Learn: Advanced Hunting schema
- Microsoft Learn: Web browser cookies in Entra authentication
- Microsoft Learn: Prevent Cross-Site Request Forgery (XSRF/CSRF)
- W3Tutorials: Difference between X-XSRF-TOKEN and X-CSRF-TOKEN
- Daily Security Review: Cookie Bite Attack and Microsoft Session Tokens
- Workplace Ninja Connect 2026 Netherlands











