Intune reports tell you whether a policy applied. Advanced hunting in the Microsoft Defender portal tells you what is actually on the device right now: the OS build, the Chrome version, the CVEs, who ran PsExec last Tuesday. In this post I'll share eight short queries I keep coming back to, each using only tables and columns from Microsoft's published schema, plus the limits you need to know and how to turn a query into a saved query, an export or a custom detection.
Prerequisites#
- Advanced hunting needs Defender for Endpoint Plan 2 (or Defender XDR). Vulnerability tables need Defender Vulnerability Management data, which Plan 2 includes.
- Permissions: a Microsoft Entra role such as Security Reader, or the Defender XDR unified RBAC permission Security data basics (read). If Defender for Endpoint RBAC is on, your device group assignments decide which devices you see.
- Open Hunting › Advanced hunting, switch to advanced mode, and set the time range picker. Queries work in UTC; results are shown in your configured time zone.
Note: DeviceInfo and the other entity tables receive a fresh full record for each device roughly hourly, so a device appears many times. The summarize arg_max(Timestamp, *) by DeviceId pattern below keeps only the latest record per device; the DeviceTvm* tables are snapshots and don't need it.
Step-by-step: the queries#
1. Devices by OS platform, version and build#
DeviceInfo
| where Timestamp > ago(7d) and isnotempty(OSPlatform)
| summarize arg_max(Timestamp, *) by DeviceId
| summarize Devices = dcount(DeviceId) by OSPlatform, OSVersion, OSBuild
| order by Devices descCompare this with your Intune feature update rings: a build that should be gone but still shows up here is a device the ring never reached.
2. Onboarding and sensor health distribution#
DeviceInfo
| where Timestamp > ago(7d) and isnotempty(OSPlatform)
| summarize arg_max(Timestamp, *) by DeviceId
| summarize Devices = dcount(DeviceId), Sample = any(DeviceName)
by OnboardingStatus, SensorHealthState, OSPlatform
| order by Devices descAnything other than an onboarded device with an active sensor deserves a look. Add ClientVersion and JoinType to the final summarize if you suspect an old sensor build or a join-type pattern.
3. Installed versions of an app across the fleet#
DeviceTvmSoftwareInventory
| where SoftwareVendor =~ "google" and SoftwareName has "chrome"
| summarize Devices = dcount(DeviceId) by SoftwareName, SoftwareVersion, EndOfSupportStatus
| order by Devices descVendor and product names in the inventory are normalised (lowercase, underscores instead of spaces), so check the exact value on the Software inventory page before filtering with ==.
4. Devices exposed to a specific CVE#
DeviceTvmSoftwareVulnerabilities
| where CveId == "CVE-2021-44228"
| project DeviceName, OSPlatform, SoftwareVendor, SoftwareName, SoftwareVersion,
VulnerabilitySeverityLevel, RecommendedSecurityUpdate
| order by DeviceName ascTo widen it to "every critical CVE with a public exploit", join the knowledge base: join kind=inner (DeviceTvmSoftwareVulnerabilitiesKB | where IsExploitAvailable == 1 and CvssScore >= 9 | project CveId, CvssScore) on CveId.
5. Defender Antivirus posture problems#
DeviceTvmSecureConfigurationAssessment
| where ConfigurationSubcategory == "Antivirus" and IsApplicable == 1 and IsCompliant == 0
| join kind=leftouter (
DeviceTvmSecureConfigurationAssessmentKB
| project ConfigurationId, ConfigurationName, ConfigurationImpact)
on ConfigurationId
| summarize Devices = dcount(DeviceId) by ConfigurationName, ConfigurationImpact
| order by Devices descThis lists the antivirus recommendations (real-time protection, cloud protection, up-to-date definitions and so on) that devices fail, ranked by how many fail each one. For engine, platform and signature version numbers per device, use Reports › Endpoints › Device health › Microsoft Defender Antivirus health, or explore DeviceTvmInfoGatheringKB: its FieldName column tells you which keys exist inside DeviceTvmInfoGathering.AdditionalFields, including the antivirus version fields.
6. Who ran a specific tool#
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("psexec.exe", "psexec64.exe")
| summarize Runs = count(), LastRun = max(Timestamp),
CommandLines = make_set(ProcessCommandLine, 5)
by DeviceName, AccountDomain, AccountName, InitiatingProcessFileName
| order by LastRun descSwap the file names for whatever you're tracking: an unapproved remote-support tool, a scripting host, an installer. InitiatingProcessFileName tells you whether a person launched it or a deployment agent did.
7. ASR audit events by rule#
DeviceEvents
| where Timestamp > ago(30d)
| where ActionType startswith "Asr" and ActionType endswith "Audited"
| summarize Hits = count(), Devices = dcount(DeviceId), Files = dcount(FileName)
by ActionType, InitiatingProcessFileName
| order by Hits descEach rule reports with its own action type ending in Audited or Blocked. My earlier post on rolling out ASR rules in audit mode covers how to turn these numbers into exclusions.
8. Which devices still talk to a host#
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteUrl has "wsus.contoso.local"
| summarize Connections = count(), Processes = make_set(InitiatingProcessFileName, 10)
by DeviceName, RemoteUrl, RemotePort
| order by Connections descUseful when decommissioning a WSUS or Configuration Manager server, a legacy proxy or an on-premises file share: it shows the devices and processes that still depend on it.
Verify#
Sanity-check a new query before you trust it. Run it with | count first to size the result, confirm the device total in query 1 roughly matches the Defender device inventory for the same filter, and spot-check one device against its Intune device page (OS build) or its Defender device page (software inventory). After a query runs, the portal shows execution time and a resource usage indicator (Low, Medium, High); High means you should filter earlier or project fewer columns.
Tips and gotchas#
- Limits. Native Defender XDR data is kept for 30 days. A query can return up to 100,000 rows and 64 MB of results and may run for up to 10 minutes. CPU is a per-tenant quota refreshed every 15 minutes; hit 100% and queries are blocked until the next cycle. Longer retention requires onboarding a Sentinel workspace or the streaming API.
- Save and share. Use Save in the query editor to keep a query under your queries or the shared queries, which colleagues with hunting access can run.
- Export. The Export action downloads the result set as CSV; the row and size limits above apply.
- Custom detections. Create detection rule turns a query into a scheduled check (every 24, 12 or 3 hours, hourly, or Continuous near-real-time for single-table queries without joins). Include
Timestamp,ReportIdandDeviceIdin the output, and don't filter onTimestamp, because the service pre-filters by the lookback period. Each run can raise at most 150 alerts. You need Security settings (manage) or the Security Administrator role; Security Operators also need Manage security settings in Defender for Endpoint RBAC. - Performance. Filter by time first, prefer
hasovercontains, avoid three-character search terms, and put the smaller table on the left of ajoin.