In my environment, the transition from local admin rights to standard users left behind a trail of “shadow IT”—Zoom, random personal tools, and legacy software that shouldn’t be there.

I don’t manually check every device. Instead, I’ve built a workflow that finds these apps across the fleet and removes them automatically.

The Discovery: Fleet-wide Hunting with KQL

Before I can remove anything, I need to know exactly where the problems are. I use Microsoft Defender for Endpoint (MDE) to find these apps in seconds. Instead of browsing the portal, I run a KQL query in Advanced Hunting to identify high-risk software:

// My Discovery Query (Sample)
DeviceTvmSoftwareInventory
| where SoftwareName has_any ("utorrent", "anydesk", "ccleaner")
| project DeviceName, SoftwareName, SoftwareVersion

This gives me my target list. For a more aggressive approach that handles EOL and EOS software, I use my Zombie Software Hunting workflow.

The Automation: Intune Proactive Remediation

Once I have my targets, I use Intune Proactive Remediation. My setup uses two scripts:

  1. Detection - Checks if the app exists (exit 1 = found, triggering the kill)
  2. Remediation - Silent removal

My Configuration

I target apps by registry, WMI, or the programs list. I typically configure my detection by setting these variables:

$AppDisplayName = "Zoom"
$AppPublisher = ""
$AppProductCode = "{86B70A45-00A6-4CBD-97A8-464A1254D179}"
$UsePartialMatch = $true

Full script: Detect-UnwantedApp.ps1

The Kill (Remediation)

Once detected, my remediation script pulls the uninstall string from the registry or uses the MSI product code to wipe the app silently.

Full script: Remove-UnwantedApp.ps1

Deployment Details

This is how I set it up in my tenant:

  1. IntuneDevicesRemediations
  2. Create script package: “Remove [AppName]”
  3. Settings:
    • Run script in 64-bit PowerShell: Yes
    • Run with logged-on credentials: No (runs as SYSTEM)
  4. Schedule: Daily (I want these gone fast)

Trade-offs and Lessons Learned

  • Pilot first - I always run detection on a pilot group before enabling the remediation.
  • Product codes change - I’ve learned that different versions often have different codes; KQL helps me find all the variants first.
  • Partial match risk - I use $UsePartialMatch = $true carefully to avoid catching plugins I might actually want to keep.