<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Automation on Eriteach | Microsoft Cloud Tech</title><link>https://blog.eriteach.com/en/tags/automation/</link><description>Recent content in Automation on Eriteach | Microsoft Cloud Tech</description><generator>Hugo -- 0.155.1</generator><language>en</language><copyright>2024-2026 Robel Mehari. All rights reserved.</copyright><lastBuildDate>Thu, 23 Apr 2026 12:28:16 +0200</lastBuildDate><atom:link href="https://blog.eriteach.com/en/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>Hunting Zombie Software: How I Automate the Removal of Unauthorized and EOS Apps</title><link>https://blog.eriteach.com/en/posts/zombie-apps-removal-intune-proactive-remediation/</link><pubDate>Wed, 15 Apr 2026 10:00:00 +0200</pubDate><guid>https://blog.eriteach.com/en/posts/zombie-apps-removal-intune-proactive-remediation/</guid><description>My approach to using KQL in Defender to hunt down unauthorized and EOS software, and how I use Intune Proactive Remediations to kill them at scale.</description><content:encoded><![CDATA[<p>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 &ldquo;handy&rdquo; 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).</p>
<p>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 &ldquo;kill&rdquo; via Intune.</p>
<h2 id="the-problem-shadow-it-and-defender-alarms">The Problem: Shadow IT and Defender Alarms</h2>
<p>I&rsquo;ve seen it time and again: Defender&rsquo;s vulnerability management flags 50 devices with an outdated version of GIMP or a random browser like Yandex. These aren&rsquo;t apps we deployed; they are &ldquo;zombie&rdquo; software—apps that shouldn&rsquo;t be there, aren&rsquo;t getting patches, and create massive operational noise in our security reports.</p>
<h2 id="step-1-hunting-at-scale-with-kql">Step 1: Hunting at Scale with KQL</h2>
<p>I don&rsquo;t waste time browsing the Defender portal app-by-app. Instead, I use <strong>KQL (Kusto Query Language)</strong> in Advanced Hunting to get immediate clarity across the entire fleet.</p>
<p>I developed this script to categorize apps by risk (High/Medium/Low), join them with known CVEs, and calculate an <strong>Exposure Score</strong>. This is how I decide exactly what needs to be removed immediately and what can wait.</p>
<p>You can find my full KQL hunting scripts here: <a href="https://github.com/Thugney/eriteach-scripts/blob/main/intune/hunting/unwanted-apps.kql">unwanted-apps.kql</a> and <a href="https://github.com/Thugney/eriteach-scripts/blob/main/intune/hunting/eos-and-unwanted-summary.kql">eos-and-unwanted-summary.kql</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">// MY UNWANTED APP HUNTER - Fleet-wide Discovery (Sample List)
</span></span><span class="line"><span class="cl">let HighRiskApps = dynamic([&#34;utorrent&#34;, &#34;anydesk&#34;, &#34;app_placeholder_1&#34;]);
</span></span><span class="line"><span class="cl">let MediumRiskApps = dynamic([&#34;steam&#34;, &#34;bluestacks&#34;, &#34;app_placeholder_2&#34;]);
</span></span><span class="line"><span class="cl">let LowRiskApps = dynamic([&#34;ccleaner&#34;, &#34;app_placeholder_3&#34;]);
</span></span><span class="line"><span class="cl">let UnwantedRegex = strcat(@&#34;(?i)(&#34;, strcat_array(HighRiskApps, &#34;|&#34;), &#34;|&#34;, strcat_array(MediumRiskApps, &#34;|&#34;), &#34;|&#34;, strcat_array(LowRiskApps, &#34;|&#34;), &#34;)&#34;);
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">// Inventory &amp; CVE Correlation
</span></span><span class="line"><span class="cl">DeviceTvmSoftwareInventory
</span></span><span class="line"><span class="cl">| where isnotempty(SoftwareName)
</span></span><span class="line"><span class="cl">| extend MatchTarget = tolower(strcat(coalesce(SoftwareVendor, &#34;&#34;), &#34; &#34;, SoftwareName))
</span></span><span class="line"><span class="cl">| where MatchTarget matches regex UnwantedRegex
</span></span><span class="line"><span class="cl">| join kind=leftouter (
</span></span><span class="line"><span class="cl">    DeviceTvmSoftwareVulnerabilities
</span></span><span class="line"><span class="cl">    | summarize CveCount = dcount(CveId), MaxCvss = max(todouble(CvssScore)) by DeviceId, SoftwareName
</span></span><span class="line"><span class="cl">) on DeviceId, SoftwareName
</span></span><span class="line"><span class="cl">| extend ExposureScore = case(MatchTarget matches regex @&#34;(?i)(utorrent|bittorrent|anydesk)&#34;, 80.0, 40.0) // My scoring logic
</span></span><span class="line"><span class="cl">| project DeviceName, SoftwareName, SoftwareVersion, CveCount, MaxCvss, ExposureScore
</span></span><span class="line"><span class="cl">| order by ExposureScore desc
</span></span></code></pre></div><p>This KQL gives me my &ldquo;hit list.&rdquo; Once I have the targets identified by the query, I move to automation.</p>
<h2 id="step-2-automating-the-kill-with-intune">Step 2: Automating the Kill with Intune</h2>
<p>Once my KQL hunt identifies the targets, I use a &ldquo;Master Script&rdquo; approach for Intune Proactive Remediations. I don&rsquo;t create separate packages for every single app; I use a centralized target list based on my KQL findings.</p>
<h3 id="the-strategy">The Strategy</h3>
<p>The system uses two scripts: a <strong>Detection</strong> script that identifies the presence of the software found in my KQL hunt, and a <strong>Remediation</strong> script that kills it.</p>
<p>I target these &ldquo;zombies&rdquo; using three methods:</p>
<ol>
<li><strong>Registry GUIDs:</strong> Finding the specific MSI or installer GUID.</li>
<li><strong>Wildcard Registry:</strong> Finding keys like <code>HKLM:\... \Uninstall\Opera*</code>.</li>
<li><strong>File Paths:</strong> Checking for specific executables.</li>
</ol>
<p>Crucially, my scripts handle <strong>per-user installs</strong> by scanning the <code>HKEY_USERS</code> (HKU) hive, ensuring that apps hiding in a user&rsquo;s local profile don&rsquo;t escape.</p>
<h2 id="implementation-the-master-scripts">Implementation: The Master Scripts</h2>
<p>I keep the full scripts in my <a href="https://github.com/Thugney/eriteach-scripts/tree/main/intune/remediations">GitHub repository</a>.</p>
<h3 id="1-the-detection-script">1. The Detection Script</h3>
<p>The script iterates through a <code>$zombieTargets</code> array. If any target identified in my KQL hunt is found on a device, it exits with <strong>Exit 1</strong>, triggering the remediation.</p>
<h3 id="2-the-remediation-script">2. The Remediation Script</h3>
<p>When triggered, the script attempts a silent uninstall via the registry. If that fails (which it often does with user-installed &ldquo;zombies&rdquo;), it forcefully stops any running processes and nukes the application folder.</p>
<h2 id="verification-closing-the-loop">Verification: Closing the Loop</h2>
<p>After deploying the remediation, I close the loop by:</p>
<ol>
<li><strong>Intune Console:</strong> Monitoring the &ldquo;Issue fixed&rdquo; status in the Remediations blade.</li>
<li><strong>Local Logs:</strong> Checking <code>C:\ProgramData\Eriteach\Logs\</code> if I need to see why a specific uninstall failed.</li>
<li><strong>Defender for Endpoint:</strong> Re-running my KQL query a few days later to watch the exposure score drop as the zombie apps disappear.</li>
</ol>
<h2 id="summary">Summary</h2>
<p>I don&rsquo;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&rsquo;ve managed to keep my environment clean without manual intervention.</p>
]]></content:encoded></item><item><title>Removing Firefox at Scale: My Intune Proactive Remediation Approach</title><link>https://blog.eriteach.com/en/posts/intune-proactive-remediation-remove-firefox/</link><pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.eriteach.com/en/posts/intune-proactive-remediation-remove-firefox/</guid><description>My approach to detecting and removing Firefox using KQL for scoping and Intune for automated cleanup.</description><content:encoded><![CDATA[<p>When Mozilla Firefox is marked as unsanctioned in our environment, my next move is to clean up existing installs across all managed devices.</p>
<p>Before we standardized our browsers, users were free to install whatever they liked, which left Firefox scattered across hundreds of endpoints. I don&rsquo;t waste time with manual cleanup; I&rsquo;ve automated the entire process.</p>
<h2 id="scoping-at-scale-with-kql">Scoping at Scale with KQL</h2>
<p>I start by getting a clear picture of the &ldquo;infestation.&rdquo; I use <strong>KQL (Kusto Query Language)</strong> in Defender Advanced Hunting to see exactly which versions are out there and how many devices are affected:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">// My Firefox Discovery Query
</span></span><span class="line"><span class="cl">DeviceTvmSoftwareInventory
</span></span><span class="line"><span class="cl">| where SoftwareName contains &#34;Firefox&#34;
</span></span><span class="line"><span class="cl">| summarize DeviceCount = dcount(DeviceName), Versions = make_set(SoftwareVersion) by SoftwareName
</span></span><span class="line"><span class="cl">| order by DeviceCount desc
</span></span></code></pre></div><p>This query gives me an immediate count and version list, which I use to target my Intune remediations effectively.</p>
<h2 id="the-strategy-automating-the-cleanup">The Strategy: Automating the Cleanup</h2>
<p>I use Intune Proactive Remediation with a two-script system:</p>
<ul>
<li><strong>Detection</strong>: Finds Firefox in the registry, Program Files, and user profiles.</li>
<li><strong>Remediation</strong>: Wipes everything—processes, files, shortcuts, services, and scheduled tasks.</li>
</ul>
<h3 id="detection-logic">Detection Logic</h3>
<p>My script scans three main areas where Firefox likes to hide:</p>
<ol>
<li><strong>Registry</strong> - 64-bit/32-bit uninstall keys and per-user installs.</li>
<li><strong>Program Files</strong> - Standard install locations.</li>
<li><strong>User Profiles</strong> - AppData folders.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$findings</span> <span class="p">=</span> <span class="vm">@</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nv">$uninstallPaths</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$path</span> <span class="k">in</span> <span class="nv">$uninstallPaths</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nb">Test-Path</span> <span class="nv">$path</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$apps</span> <span class="p">=</span> <span class="nb">Get-ItemProperty</span> <span class="s2">&#34;</span><span class="nv">$path</span><span class="s2">\*&#34;</span> <span class="n">-ErrorAction</span> <span class="n">SilentlyContinue</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">            <span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="py">DisplayName</span> <span class="o">-like</span> <span class="s2">&#34;*Firefox*&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$app</span> <span class="k">in</span> <span class="nv">$apps</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$findings</span> <span class="p">+=</span> <span class="s2">&#34;Registry: </span><span class="p">$(</span><span class="nv">$app</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$findings</span><span class="p">.</span><span class="py">Count</span> <span class="o">-gt</span> <span class="mf">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">exit</span> <span class="mf">1</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">exit</span> <span class="mf">0</span> <span class="p">}</span>
</span></span></code></pre></div><p>Full script: <a href="https://github.com/Thugney/eriteach-scripts/blob/main/intune/remediations/firefox-removal-detection.ps1">firefox-removal-detection.ps1</a></p>
<h2 id="the-kill-remediation">The Kill: Remediation</h2>
<p>My remediation script is designed to be thorough. It stops all active processes before attempting the uninstall to ensure nothing is locked.</p>
<ol>
<li><strong>Stop Processes</strong> - Firefox, plugin-container, and updaters.</li>
<li><strong>Uninstall</strong> - Uses the registry uninstall string (handles helper.exe and msiexec).</li>
<li><strong>Cleanup</strong> - Deletes directories in Program Files, ProgramData, and AppData.</li>
<li><strong>Final Polish</strong> - Removes shortcuts, the MozillaMaintenance service, and update tasks.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># My Process Kill List</span>
</span></span><span class="line"><span class="cl"><span class="nv">$firefoxProcesses</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span><span class="s2">&#34;firefox&#34;</span><span class="p">,</span> <span class="s2">&#34;firefox-esr&#34;</span><span class="p">,</span> <span class="s2">&#34;plugin-container&#34;</span><span class="p">,</span> <span class="s2">&#34;crashreporter&#34;</span><span class="p">,</span> <span class="s2">&#34;updater&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$proc</span> <span class="k">in</span> <span class="nv">$firefoxProcesses</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Get-Process</span> <span class="n">-Name</span> <span class="nv">$proc</span> <span class="n">-ErrorAction</span> <span class="n">SilentlyContinue</span> <span class="p">|</span> <span class="nb">Stop-Process</span> <span class="n">-Force</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Full script: <a href="https://github.com/Thugney/eriteach-scripts/blob/main/intune/remediations/firefox-removal-remediation.ps1">firefox-removal-remediation.ps1</a></p>
<h2 id="outcome-and-verification">Outcome and Verification</h2>
<p>I monitor the progress directly in the Intune portal under <strong>Devices</strong> &gt; <strong>Scripts and remediations</strong>.</p>
<p>Success looks like a &ldquo;fixed&rdquo; status on my devices. I also check my custom logs at <code>C:\ProgramData\Eriteach\Logs\</code> if I see any stubborn installs that require a manual look.</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<ul>
<li><strong>User Profiles</strong> - The script cleans all profiles on the machine. I always warn users that bookmarks and saved passwords will be gone.</li>
<li><strong>Force Close</strong> - Since I force-close the process, I run this remediation during off-hours or maintenance windows to minimize disruption.</li>
</ul>
<h2 id="related-links">Related Links</h2>
<ul>
<li><a href="/en/posts/intune-proactive-remediation-firefox-update/">Auto-Update Firefox with Intune</a> - My workflow for when I need to keep Firefox but stay updated.</li>
<li><a href="https://learn.microsoft.com/en-us/mem/intune/fundamentals/remediations">Intune Remediations overview</a></li>
<li><a href="https://learn.microsoft.com/en-us/defender-endpoint/software-inventory">Microsoft Defender software inventory</a></li>
</ul>
]]></content:encoded></item><item><title>Removing Unauthorized Apps: My Intune Proactive Remediation Workflow</title><link>https://blog.eriteach.com/en/posts/remove-unauthorized-apps-intune-proactive-remediation/</link><pubDate>Sun, 01 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.eriteach.com/en/posts/remove-unauthorized-apps-intune-proactive-remediation/</guid><description>My workflow for detecting and removing unauthorized applications using KQL for discovery and Intune for automated cleanup.</description><content:encoded><![CDATA[<p>In my environment, the transition from local admin rights to standard users left behind a trail of &ldquo;shadow IT&rdquo;—Zoom, random personal tools, and legacy software that shouldn&rsquo;t be there.</p>
<p>I don&rsquo;t manually check every device. Instead, I&rsquo;ve built a workflow that finds these apps across the fleet and removes them automatically.</p>
<h2 id="the-discovery-fleet-wide-hunting-with-kql">The Discovery: Fleet-wide Hunting with KQL</h2>
<p>Before I can remove anything, I need to know exactly where the problems are. I use <strong>Microsoft Defender for Endpoint (MDE)</strong> to find these apps in seconds. Instead of browsing the portal, I run a KQL query in Advanced Hunting to identify high-risk software:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">// My Discovery Query (Sample)
</span></span><span class="line"><span class="cl">DeviceTvmSoftwareInventory
</span></span><span class="line"><span class="cl">| where SoftwareName has_any (&#34;utorrent&#34;, &#34;anydesk&#34;, &#34;ccleaner&#34;)
</span></span><span class="line"><span class="cl">| project DeviceName, SoftwareName, SoftwareVersion
</span></span></code></pre></div><p>This gives me my target list. For a more aggressive approach that handles EOL and EOS software, I use my <a href="https://blog.eriteach.com/en/posts/zombie-apps-removal-intune-proactive-remediation/">Zombie Software Hunting</a> workflow.</p>
<h2 id="the-automation-intune-proactive-remediation">The Automation: Intune Proactive Remediation</h2>
<p>Once I have my targets, I use Intune Proactive Remediation. My setup uses two scripts:</p>
<ol>
<li><strong>Detection</strong> - Checks if the app exists (exit 1 = found, triggering the kill)</li>
<li><strong>Remediation</strong> - Silent removal</li>
</ol>
<h3 id="my-configuration">My Configuration</h3>
<p>I target apps by registry, WMI, or the programs list. I typically configure my detection by setting these variables:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$AppDisplayName</span> <span class="p">=</span> <span class="s2">&#34;Zoom&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$AppPublisher</span> <span class="p">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$AppProductCode</span> <span class="p">=</span> <span class="s2">&#34;{86B70A45-00A6-4CBD-97A8-464A1254D179}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$UsePartialMatch</span> <span class="p">=</span> <span class="vm">$true</span>
</span></span></code></pre></div><p>Full script: <a href="https://github.com/Thugney/eriteach-scripts/blob/main/proactive-remediation/Detect-UnwantedApp.ps1">Detect-UnwantedApp.ps1</a></p>
<h3 id="the-kill-remediation">The Kill (Remediation)</h3>
<p>Once detected, my remediation script pulls the uninstall string from the registry or uses the MSI product code to wipe the app silently.</p>
<p>Full script: <a href="https://github.com/Thugney/eriteach-scripts/blob/main/proactive-remediation/Remove-UnwantedApp.ps1">Remove-UnwantedApp.ps1</a></p>
<h2 id="deployment-details">Deployment Details</h2>
<p>This is how I set it up in my tenant:</p>
<ol>
<li><strong>Intune</strong> → <strong>Devices</strong> → <strong>Remediations</strong></li>
<li><strong>Create script package</strong>: &ldquo;Remove [AppName]&rdquo;</li>
<li><strong>Settings</strong>:
<ul>
<li>Run script in 64-bit PowerShell: <strong>Yes</strong></li>
<li>Run with logged-on credentials: <strong>No</strong> (runs as SYSTEM)</li>
</ul>
</li>
<li><strong>Schedule</strong>: Daily (I want these gone fast)</li>
</ol>
<h2 id="trade-offs-and-lessons-learned">Trade-offs and Lessons Learned</h2>
<ul>
<li><strong>Pilot first</strong> - I always run detection on a pilot group before enabling the remediation.</li>
<li><strong>Product codes change</strong> - I&rsquo;ve learned that different versions often have different codes; KQL helps me find all the variants first.</li>
<li><strong>Partial match risk</strong> - I use <code>$UsePartialMatch = $true</code> carefully to avoid catching plugins I might actually want to keep.</li>
</ul>
<h2 id="related-links">Related Links</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/mem/intune/fundamentals/remediations">Microsoft: Proactive Remediations</a></li>
<li><a href="https://learn.microsoft.com/en-us/mem/intune/apps/intune-management-extension">Intune script requirements</a></li>
</ul>
]]></content:encoded></item></channel></rss>