Detect missing MDE licensing.
Use this guide when Defender XDR does not show endpoint devices or Sentinel Device tables are not populating. The goal is to separate license entitlement, workload provisioning, endpoint onboarding, connector setup, and Sentinel ingestion.
Key distinction
Office 365 E5 is not the same bundle as Microsoft 365 E5. Office 365 E5 includes productivity services and Office security features, including Defender for Office 365 capabilities. It does not automatically grant the full Defender for Endpoint Plan 2 entitlement. Microsoft 365 E5 is broader and includes Defender for Endpoint Plan 2.
| License state | Expected MDE device inventory | Expected Sentinel Device tables |
|---|---|---|
| Office 365 E5 only | Not sufficient | Not sufficient |
| Defender for Endpoint Plan 1 | Limited endpoint protection | Do not assume full P2 advanced hunting and Sentinel Device table coverage |
| Defender for Endpoint Plan 2 | Yes, after workload provisioning and endpoint onboarding | Yes, after connector configuration and billable event streaming |
| Microsoft 365 E5 | Yes, includes MDE P2 | Yes, after connector configuration and billable event streaming |
| Defender for Business | Separate SMB SKU | Verify in tenant; do not use as a P2-equivalent control without checking current entitlement behavior |
Detection signals
| Strength | Signal |
|---|---|
| Strong | Assets > Devices or /machines is missing or unavailable in the Defender portal. |
| Strong | Settings > Endpoints is missing or does not expose onboarding and device inventory settings. |
| Strong | The MDE machines API returns 403 with Account mode is inactive. |
| Strong | Graph subscribedSkus shows Office security service plans, but no Defender for Endpoint entitlement candidate. |
| Strong | The Microsoft Defender XDR connector cannot produce MDE Device* advanced hunting tables in Sentinel. |
| Weak | The user is Global Administrator. |
| Weak | The user has an E5-looking license. |
| Weak | Entra ID contains device objects or a Windows device is Entra joined. |
Required data
| Data point | Source | Why it matters |
|---|---|---|
| Tenant ID | az account show, Graph /organization | Prevents testing the wrong tenant |
| Subscribed SKUs | Graph /subscribedSkus | Shows purchased licenses and service plans |
| User license assignment | Graph /users/{id} or /me | Confirms the test user has the expected license assigned |
| Directory roles | Graph /directoryRoles and /memberOf | Separates role assignment from licensing |
| Organization provisioned plans | Graph /organization?$select=provisionedPlans | Shows tenant-level workload provisioning |
| MDE machine API status | api.securitycenter.microsoft.com/api/machines | Confirms whether the MDE workload is active |
| Sentinel table activity | Log Analytics KQL | Confirms whether data is flowing after licensing, onboarding, and connector setup |
Az CLI detection
az account show \
--query "{tenantId:tenantId, tenantDisplayName:tenantDisplayName, tenantDefaultDomain:tenantDefaultDomain, user:user.name, subscription:name}" \
--output tableaz rest \
--method GET \
--url https://graph.microsoft.com/v1.0/subscribedSkus \
--query "value[].{sku:skuPartNumber, consumed:consumedUnits, enabled:prepaidUnits.enabled, plans:servicePlans[?contains(servicePlanName, 'DEFENDER') || contains(servicePlanName, 'ATP') || contains(servicePlanName, 'MTP') || contains(servicePlanName, 'INTUNE')].{name:servicePlanName,status:provisioningStatus,appliesTo:appliesTo}}" \
--output jsonaz rest \
--method GET \
--url 'https://graph.microsoft.com/v1.0/me?$select=id,displayName,userPrincipalName,assignedLicenses,assignedPlans' \
--output jsonaz rest \
--method GET \
--url 'https://graph.microsoft.com/v1.0/organization?$select=id,displayName,provisionedPlans' \
--output jsonUSER_ID="$(az rest \
--method GET \
--url 'https://graph.microsoft.com/v1.0/me?$select=id' \
--query id \
--output tsv)"
az rest \
--method GET \
--url "https://graph.microsoft.com/v1.0/users/${USER_ID}/memberOf?\$select=id,displayName,roleTemplateId" \
--query "value[?displayName=='Global Administrator']" \
--output tableaz rest \
--resource https://api.securitycenter.microsoft.com \
--method GET \
--url 'https://api.securitycenter.microsoft.com/api/machines?$top=1' \
--output json| Result | Meaning |
|---|---|
| 200 OK with value array | MDE account is active and API is reachable; no devices can still mean no endpoints are onboarded. |
| 403 with Account mode is inactive | Strong evidence that the MDE workload or account is not active for the tenant. |
| 401 or invalid authorization payload | Token/resource mismatch; retry with --resource https://api.securitycenter.microsoft.com. |
| 403 permission text without inactive account | MDE may be active, but the caller lacks Defender permissions. |
PowerShell detection
Connect-MgGraph -Scopes @(
"Organization.Read.All",
"User.Read.All",
"Directory.Read.All"
)
$skus = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/subscribedSkus"
$skus.value | ForEach-Object {
$sku = $_
$sku.servicePlans |
Where-Object { $_.servicePlanName -match "DEFENDER|ATP|MTP|INTUNE|WINDEF" } |
Select-Object `
@{Name = "Sku"; Expression = { $sku.skuPartNumber } },
servicePlanName,
provisioningStatus,
appliesTo
}$token = az account get-access-token `
--resource https://api.securitycenter.microsoft.com `
--query accessToken `
--output tsv
try {
Invoke-RestMethod `
-Method GET `
-Uri "https://api.securitycenter.microsoft.com/api/machines?`$top=1" `
-Headers @{ Authorization = "Bearer $token" }
} catch {
$_.Exception.Response.StatusCode.value__
$_.ErrorDetails.Message
}Terraform and CI gate
Terraform is best used as a policy gate here, not the primary license inventory engine. Query Graph and the live MDE API before applying Sentinel content that assumes DeviceEvents or other MDE Device tables.
#!/usr/bin/env bash
set -euo pipefail
subscribed_skus="$(az rest \
--method GET \
--url https://graph.microsoft.com/v1.0/subscribedSkus \
--output json)"
mde_status="unknown"
mde_error=""
set +e
mde_response="$(az rest \
--resource https://api.securitycenter.microsoft.com \
--method GET \
--url 'https://api.securitycenter.microsoft.com/api/machines?$top=1' \
--output json 2>&1)"
mde_exit=$?
set -e
if [ "$mde_exit" -eq 0 ]; then
mde_status="active"
elif printf '%s' "$mde_response" | grep -qi "Account mode is inactive"; then
mde_status="inactive"
mde_error="Account mode is inactive"
elif printf '%s' "$mde_response" | grep -qi "Forbidden"; then
mde_status="forbidden"
mde_error="Forbidden without inactive-account marker"
else
mde_status="error"
mde_error="$mde_response"
fi
has_enterprisepremium="$(printf '%s' "$subscribed_skus" |
jq -r '[.value[] | select(.skuPartNumber == "ENTERPRISEPREMIUM")] | length > 0')"
defender_plan_names="$(printf '%s' "$subscribed_skus" |
jq -r '[.value[].servicePlans[].servicePlanName |
select(test("DEFENDER|ATP|MTP|INTUNE|WINDEF"; "i"))] | unique | join(",")')"
jq -n \
--arg has_enterprisepremium "$has_enterprisepremium" \
--arg defender_plan_names "$defender_plan_names" \
--arg mde_status "$mde_status" \
--arg mde_error "$mde_error" \
'{
has_enterprisepremium: $has_enterprisepremium,
defender_plan_names: $defender_plan_names,
mde_status: $mde_status,
mde_error: $mde_error
}'data "external" "mde_license_check" {
program = ["bash", "${path.module}/scripts/check-mde-license.sh"]
}
locals {
mde_status = data.external.mde_license_check.result.mde_status
mde_error = data.external.mde_license_check.result.mde_error
}
check "mde_is_active_before_sentinel_device_tables" {
assert {
condition = local.mde_status == "active"
error_message = "Microsoft Defender for Endpoint is not active for this tenant. Status: ${local.mde_status}. Error: ${local.mde_error}. Do not deploy Sentinel content that assumes DeviceEvents or other MDE Device tables."
}
}Sentinel verification flow
flowchart TD A[MDE license assigned] --> B[MDE workload provisioned] B --> C[Endpoint onboarded] C --> D[Defender XDR advanced hunting receives Device events] D --> E[Microsoft Defender XDR connector enabled in Sentinel] E --> F[MDE advanced hunting tables selected for streaming] F --> G[Log Analytics receives Device tables]
Defender XDR check
DeviceInfo
| take 10
DeviceEvents
| summarize Events=count(), FirstSeen=min(Timestamp), LastSeen=max(Timestamp)Sentinel Log Analytics check
DeviceInfo
| summarize Rows=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated)
union isfuzzy=true
DeviceEvents,
DeviceProcessEvents,
DeviceNetworkEvents,
DeviceFileEvents,
DeviceRegistryEvents,
DeviceLogonEvents,
DeviceImageLoadEvents
| summarize Rows=count() by Type
| order by Rows descDecision tree
flowchart TD
A{Only Office 365 E5 / ENTERPRISEPREMIUM?} -->|Yes| B{MDE machines API says account inactive?}
B -->|Yes| C[Missing or inactive MDE entitlement]
B -->|No| D{Permission-specific forbidden?}
D -->|Yes| E[Fix Defender RBAC or API permissions]
D -->|No| F[Check portal provisioning service health and onboarding]
A -->|No| G{MDE entitlement candidate present?}
G -->|Yes| H{User license assigned?}
H -->|No| I[Assign license]
H -->|Yes| J{MDE API active?}
J -->|No| K[Provision workload and check service health]
J -->|Yes| L[Onboard endpoint and validate hunting/Sentinel tables]Remediation
- Buy and assign Microsoft Defender for Endpoint Plan 2 standalone, or move the user to Microsoft 365 E5.
- Assign the license to the test/admin user.
- Open security.microsoft.com/machines and security.microsoft.com/securitysettings/endpoints.
- Wait for provisioning and re-run the MDE machines API probe.
- Onboard one endpoint.
- Confirm DeviceInfo in Defender XDR advanced hunting.
- Enable the Microsoft Defender XDR connector in Sentinel.
- Select MDE advanced hunting tables for streaming.
- Confirm DeviceInfo and DeviceEvents in Log Analytics.
Rules of thumb
- Do not use Global Administrator as a proxy for MDE readiness.
- Do not use ENTERPRISEPREMIUM as proof of Defender for Endpoint.
- Do not use Entra device count as proof of MDE onboarding.
- Do not use MTP alone as proof of MDE P2.
- Treat Account mode is inactive from the MDE API as a hard failure for endpoint inventory and Sentinel MDE event streaming.
- Use both Graph subscribed SKU classification and a live MDE API activation probe.
- Keep service-plan allowlists configurable because Microsoft changes packaging, trials, and cloud-specific SKU details.