Operator guide

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 stateExpected MDE device inventoryExpected Sentinel Device tables
Office 365 E5 onlyNot sufficientNot sufficient
Defender for Endpoint Plan 1Limited endpoint protectionDo not assume full P2 advanced hunting and Sentinel Device table coverage
Defender for Endpoint Plan 2Yes, after workload provisioning and endpoint onboardingYes, after connector configuration and billable event streaming
Microsoft 365 E5Yes, includes MDE P2Yes, after connector configuration and billable event streaming
Defender for BusinessSeparate SMB SKUVerify in tenant; do not use as a P2-equivalent control without checking current entitlement behavior

Detection signals

StrengthSignal
StrongAssets > Devices or /machines is missing or unavailable in the Defender portal.
StrongSettings > Endpoints is missing or does not expose onboarding and device inventory settings.
StrongThe MDE machines API returns 403 with Account mode is inactive.
StrongGraph subscribedSkus shows Office security service plans, but no Defender for Endpoint entitlement candidate.
StrongThe Microsoft Defender XDR connector cannot produce MDE Device* advanced hunting tables in Sentinel.
WeakThe user is Global Administrator.
WeakThe user has an E5-looking license.
WeakEntra ID contains device objects or a Windows device is Entra joined.

Required data

Data pointSourceWhy it matters
Tenant IDaz account show, Graph /organizationPrevents testing the wrong tenant
Subscribed SKUsGraph /subscribedSkusShows purchased licenses and service plans
User license assignmentGraph /users/{id} or /meConfirms the test user has the expected license assigned
Directory rolesGraph /directoryRoles and /memberOfSeparates role assignment from licensing
Organization provisioned plansGraph /organization?$select=provisionedPlansShows tenant-level workload provisioning
MDE machine API statusapi.securitycenter.microsoft.com/api/machinesConfirms whether the MDE workload is active
Sentinel table activityLog Analytics KQLConfirms whether data is flowing after licensing, onboarding, and connector setup

Az CLI detection

bash
az account show \
  --query "{tenantId:tenantId, tenantDisplayName:tenantDisplayName, tenantDefaultDomain:tenantDefaultDomain, user:user.name, subscription:name}" \
  --output table
bash
az 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 json
bash
az rest \
  --method GET \
  --url 'https://graph.microsoft.com/v1.0/me?$select=id,displayName,userPrincipalName,assignedLicenses,assignedPlans' \
  --output json
bash
az rest \
  --method GET \
  --url 'https://graph.microsoft.com/v1.0/organization?$select=id,displayName,provisionedPlans' \
  --output json
bash
USER_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 table
bash
az rest \
  --resource https://api.securitycenter.microsoft.com \
  --method GET \
  --url 'https://api.securitycenter.microsoft.com/api/machines?$top=1' \
  --output json
ResultMeaning
200 OK with value arrayMDE account is active and API is reachable; no devices can still mean no endpoints are onboarded.
403 with Account mode is inactiveStrong evidence that the MDE workload or account is not active for the tenant.
401 or invalid authorization payloadToken/resource mismatch; retry with --resource https://api.securitycenter.microsoft.com.
403 permission text without inactive accountMDE may be active, but the caller lacks Defender permissions.

PowerShell detection

powershell
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
}
powershell
$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.

bash
#!/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
  }'
hcl
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

MDE licensing to Sentinel Device table 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

kql
DeviceInfo
| take 10

DeviceEvents
| summarize Events=count(), FirstSeen=min(Timestamp), LastSeen=max(Timestamp)

Sentinel Log Analytics check

kql
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 desc

Decision tree

Missing MDE entitlement decision 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

  1. Buy and assign Microsoft Defender for Endpoint Plan 2 standalone, or move the user to Microsoft 365 E5.
  2. Assign the license to the test/admin user.
  3. Open security.microsoft.com/machines and security.microsoft.com/securitysettings/endpoints.
  4. Wait for provisioning and re-run the MDE machines API probe.
  5. Onboard one endpoint.
  6. Confirm DeviceInfo in Defender XDR advanced hunting.
  7. Enable the Microsoft Defender XDR connector in Sentinel.
  8. Select MDE advanced hunting tables for streaming.
  9. 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.