Shadow IT is the natural enemy of a clean software inventory. In my environment, users with legacy local admin rights used to install whatever they wanted—unapproved browsers, outdated image editors, and “handy” utilities. These apps quickly became an IT headache, especially when Microsoft Defender for Endpoint flagged them as End-of-Life (EOL) or End-of-Support (EOS).

Instead of playing whack-a-mole with individual apps in the portal, I developed a workflow that starts with a deep hunt in Defender and ends with an automated “kill” via Intune.

The Problem: Shadow IT and Defender Alarms

I’ve seen it time and again: Defender’s vulnerability management flags 50 devices with an outdated version of GIMP or a random browser like Yandex. These aren’t apps we deployed; they are “zombie” software—apps that shouldn’t be there, aren’t getting patches, and create massive operational noise in our security reports.

Step 1: Hunting at Scale with KQL

I don’t waste time browsing the Defender portal app-by-app. Instead, I use KQL (Kusto Query Language) in Advanced Hunting to get immediate clarity across the entire fleet.

I developed this script to categorize apps by risk (High/Medium/Low), join them with known CVEs, and calculate an Exposure Score. This is how I decide exactly what needs to be removed immediately and what can wait.

You can find my full KQL hunting scripts here: unwanted-apps.kql and eos-and-unwanted-summary.kql.

// MY UNWANTED APP HUNTER - Fleet-wide Discovery (Sample List)
let HighRiskApps = dynamic(["utorrent", "anydesk", "app_placeholder_1"]);
let MediumRiskApps = dynamic(["steam", "bluestacks", "app_placeholder_2"]);
let LowRiskApps = dynamic(["ccleaner", "app_placeholder_3"]);
let UnwantedRegex = strcat(@"(?i)(", strcat_array(HighRiskApps, "|"), "|", strcat_array(MediumRiskApps, "|"), "|", strcat_array(LowRiskApps, "|"), ")");

// Inventory & CVE Correlation
DeviceTvmSoftwareInventory
| where isnotempty(SoftwareName)
| extend MatchTarget = tolower(strcat(coalesce(SoftwareVendor, ""), " ", SoftwareName))
| where MatchTarget matches regex UnwantedRegex
| join kind=leftouter (
    DeviceTvmSoftwareVulnerabilities
    | summarize CveCount = dcount(CveId), MaxCvss = max(todouble(CvssScore)) by DeviceId, SoftwareName
) on DeviceId, SoftwareName
| extend ExposureScore = case(MatchTarget matches regex @"(?i)(utorrent|bittorrent|anydesk)", 80.0, 40.0) // My scoring logic
| project DeviceName, SoftwareName, SoftwareVersion, CveCount, MaxCvss, ExposureScore
| order by ExposureScore desc

This KQL gives me my “hit list.” Once I have the targets identified by the query, I move to automation.

Step 2: Automating the Kill with Intune

Once my KQL hunt identifies the targets, I use a “Master Script” approach for Intune Proactive Remediations. I don’t create separate packages for every single app; I use a centralized target list based on my KQL findings.

The Strategy

The system uses two scripts: a Detection script that identifies the presence of the software found in my KQL hunt, and a Remediation script that kills it.

I target these “zombies” using three methods:

  1. Registry GUIDs: Finding the specific MSI or installer GUID.
  2. Wildcard Registry: Finding keys like HKLM:\... \Uninstall\Opera*.
  3. File Paths: Checking for specific executables.

Crucially, my scripts handle per-user installs by scanning the HKEY_USERS (HKU) hive, ensuring that apps hiding in a user’s local profile don’t escape.

Implementation: The Master Scripts

I keep the full scripts in my GitHub repository.

1. The Detection Script

The script iterates through a $zombieTargets array. If any target identified in my KQL hunt is found on a device, it exits with Exit 1, triggering the remediation.

2. The Remediation Script

When triggered, the script attempts a silent uninstall via the registry. If that fails (which it often does with user-installed “zombies”), it forcefully stops any running processes and nukes the application folder.

Verification: Closing the Loop

After deploying the remediation, I close the loop by:

  1. Intune Console: Monitoring the “Issue fixed” status in the Remediations blade.
  2. Local Logs: Checking C:\ProgramData\Eriteach\Logs\ if I need to see why a specific uninstall failed.
  3. Defender for Endpoint: Re-running my KQL query a few days later to watch the exposure score drop as the zombie apps disappear.

Summary

I don’t let unauthorized and outdated software bloat my vulnerability reports. By starting with a KQL hunt to identify the risk and following up with Intune automation, I’ve managed to keep my environment clean without manual intervention.