A zero in advanced hunting is a claim, not a fact
Arpit Dahal · September 2026 · 3 min read
When we started moving to application allowlisting, I had to make a call on every application in our software inventory: approve it, remove it, or allow it for named users only. To see where each one was installed, I used advanced hunting in Microsoft Defender.
Most searches behaved. Then an app that a full-name search said wasn't installed anywhere turned up the moment I searched for a single word from its name.
The trap
DeviceTvmSoftwareInventory doesn't store software names the way they appear in Windows. Stored names are lowercase, and some are collapsed into a single token. As far as I can tell, that happens with products that don't map cleanly to Defender's vulnerability catalogue. Docker Desktop is stored as docker. Lenovo's AI Meeting Manager Service is stored as ai_meeting_manager_service.
So the natural queries look right and find nothing:
// 0 rows: the stored name is just "docker"
DeviceTvmSoftwareInventory
| where SoftwareName has_any ("Docker Desktop")
| summarize Devices = dcount(DeviceId) by SoftwareName// 0 rows: the stored name is "ai_meeting_manager_service"
DeviceTvmSoftwareInventory
| where SoftwareName == "AI Meeting Manager Service"
| summarize Devices = dcount(DeviceId) by SoftwareNameNeither query errors. Both come back empty, which reads exactly like "not installed anywhere". In an allowlist review, a false zero means you never talk to the people who use the app, until enforcement blocks it on them.
What I do now
1. Treat every zero as unverified. Before I trust "not installed", I re-run the search with one distinctive fragment of the name:
DeviceTvmSoftwareInventory
| where SoftwareName contains "docker" or SoftwareName contains "meeting"
| distinct SoftwareVendor, SoftwareName2. Match on =~, not ==. == is case-sensitive and the stored names are lowercase, so an exact match typed the way the app displays its name fails. Once the fragment search shows the stored name, match it case-insensitively:
DeviceTvmSoftwareInventory
| where SoftwareName =~ "ai_meeting_manager_service"
| summarize Devices = dcount(DeviceId), DeviceNames = make_set(DeviceName)
by SoftwareVendor, SoftwareVersion3. Check the vendor, not just the name. The vendor field helped confirm that ai_meeting_manager_service really was Lenovo's before I approved it. It cuts the other way too: one utility reported its vendor as an unfilled template placeholder, which said more about where it came from than its name did. It didn't make the allowlist.
The lesson
A query that finds something proves itself. A query that finds nothing only proves it didn't match. Negative results are the least-tested output of any hunt, so for every "confirmed absent" I now want a second query that could have found it.