<#
.SYNOPSIS
Gets all Intune enrolled devices not onboarded into Microsoft Defender for Endpoint.
Matches directly on azureADDeviceId (Intune) = aadDeviceId (Defender).
.REQUIREMENTS
Install-Module Microsoft.Graph -Scope CurrentUser
#>
# ?? Configuration ??????????????????????????????????????????????????????????????
$ExportCSV = $true
$ExportPath = ".\IntuneNotInDefender_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
# ?? Login ??????????????????????????????????????????????????????????????????????
Write-Host "Opening browser for login..." -ForegroundColor Cyan
Connect-MgGraph -Scopes @(
"DeviceManagementManagedDevices.Read.All",
"SecurityEvents.Read.All",
"ThreatHunting.Read.All"
) -NoWelcome
Write-Host " Logged in as: $((Get-MgContext).Account)" -ForegroundColor Green
# ?? Step 1: Get all Defender machines via Advanced Hunting ?????????????????????
# Uses the DeviceInfo table which contains AadDeviceId for all onboarded machines
Write-Host "`nFetching Defender onboarded devices via Advanced Hunting..." -ForegroundColor Cyan
$mdeLookup = @{}
$huntingBody = @{
Query = @"
DeviceInfo
| where isnotempty(AadDeviceId)
| summarize arg_max(Timestamp, *) by AadDeviceId
| project AadDeviceId, DeviceName
"@
} | ConvertTo-Json
try {
$huntingResponse = Invoke-MgGraphRequest `
-Uri "https://graph.microsoft.com/v1.0/security/runHuntingQuery" `
-Method POST `
-Body $huntingBody `
-ContentType "application/json" `
-ErrorAction Stop
foreach ($row in $huntingResponse.results) {
if ($row.AadDeviceId) {
$mdeLookup[$row.AadDeviceId.ToLower()] = $row.DeviceName
}
}
Write-Host " Found $($mdeLookup.Count) Defender onboarded devices" -ForegroundColor Green
}
catch {
# Fallback: try beta listMachines endpoint
Write-Host " Hunting query failed, trying beta machines endpoint..." -ForegroundColor Yellow
$uri = "https://graph.microsoft.com/beta/security/microsoft.graph.security.listMachines()?`$select=aadDeviceId,computerDnsName&`$top=999"
do {
$response = Invoke-MgGraphRequest -Uri $uri -Method GET -ErrorAction Stop
foreach ($m in $response.value) {
if ($m.aadDeviceId) {
$mdeLookup[$m.aadDeviceId.ToLower()] = $m.computerDnsName
}
}
$uri = $response.'@odata.nextLink'
} while ($uri)
Write-Host " Found $($mdeLookup.Count) Defender onboarded devices" -ForegroundColor Green
}
if ($mdeLookup.Count -eq 0) {
Write-Error "Could not retrieve any Defender devices. Check that SecurityEvents.Read.All or ThreatHunting.Read.All is consented in your tenant."
Disconnect-MgGraph
exit 1
}
# ?? Step 2: Get all Intune managed devices ?????????????????????????????????????
Write-Host "Fetching Intune enrolled devices..." -ForegroundColor Cyan
$intuneDevices = [System.Collections.Generic.List[object]]::new()
$uri = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?" +
"`$select=id,deviceName,operatingSystem,osVersion,userPrincipalName," +
"serialNumber,azureADDeviceId,lastSyncDateTime,complianceState,managedDeviceOwnerType&`$top=999"
do {
$response = Invoke-MgGraphRequest -Uri $uri -Method GET
$intuneDevices.AddRange($response.value)
$uri = $response.'@odata.nextLink'
} while ($uri)
Write-Host " Found $($intuneDevices.Count) Intune devices" -ForegroundColor Green
# ?? Step 3: Compare directly ???????????????????????????????????????????????????
# Intune.azureADDeviceId == Defender.AadDeviceId — direct match, no translation
Write-Host "`nComparing..." -ForegroundColor Cyan
$results = foreach ($device in $intuneDevices) {
$inDefender = $device.azureADDeviceId -and
$mdeLookup.ContainsKey($device.azureADDeviceId.ToLower())
if (-not $inDefender) {
[PSCustomObject]@{
DeviceName = $device.deviceName
OS = $device.operatingSystem
OSVersion = $device.osVersion
UserPrincipalName = $device.userPrincipalName
SerialNumber = $device.serialNumber
IntuneDeviceId = $device.id
AzureADDeviceId = $device.azureADDeviceId
LastSync = $device.lastSyncDateTime
ComplianceState = $device.complianceState
OwnerType = $device.managedDeviceOwnerType
}
}
}
# ?? Output ?????????????????????????????????????????????????????????????????????
Write-Host "`n???????????????????????????????????????????????????" -ForegroundColor Yellow
Write-Host " Total Intune devices: $($intuneDevices.Count)" -ForegroundColor White
Write-Host " Defender onboarded: $($mdeLookup.Count)" -ForegroundColor White
Write-Host " In Intune but NOT in Defender: $($results.Count)" -ForegroundColor Red
Write-Host "???????????????????????????????????????????????????" -ForegroundColor Yellow
$results | Format-Table DeviceName, OS, UserPrincipalName, AzureADDeviceId -AutoSize
if ($ExportCSV -and $results.Count -gt 0) {
$results | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8
Write-Host "`nExported to: $ExportPath" -ForegroundColor Green
}
Write-Host "`nBreakdown by OS:" -ForegroundColor Cyan
$results | Group-Object OS | Sort-Object Count -Descending | Format-Table Name, Count -AutoSize
Disconnect-MgGraph