Script to get list of Machines not enrolled into Defender but enrolled into Intune

<#
.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
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...