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:
- Detection - Checks if the app exists (exit 1 = found, triggering the kill)
- 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:
- Intune → Devices → Remediations
- Create script package: “Remove [AppName]”
- Settings:
- Run script in 64-bit PowerShell: Yes
- Run with logged-on credentials: No (runs as SYSTEM)
- 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 = $truecarefully to avoid catching plugins I might actually want to keep.