Revise article on detecting and removing unwanted software (#49755)

Updated the article to improve clarity and fix minor grammatical issues
throughout the text, including prerequisites, policy creation, and
script instructions.

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [ ] Timeouts are implemented and retries are limited to avoid infinite
loops
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [ ] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [ ] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [ ] Confirmed that the fix is not expected to adversely impact load
test results
- [ ] Alerted the release DRI if additional load testing is needed

## Database migrations

- [ ] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [ ] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [ ] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

## New Fleet configuration settings

- [ ] Setting(s) is/are explicitly excluded from GitOps

If you didn't check the box above, follow this checklist for
GitOps-enabled settings:

- [ ] Verified that the setting is exported via `fleetctl
generate-gitops`
- [ ] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [ ] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
- [ ] Verified that any relevant UI is disabled when GitOps mode is
enabled

## fleetd/orbit/Fleet Desktop

- [ ] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [ ] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [ ] Verified that fleetd runs on macOS, Linux and Windows
- [ ] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))
This commit is contained in:
|@rm!n3
2026-07-22 11:03:50 -05:00
committed by GitHub
parent 130c5621cd
commit a980d4e2f0
@@ -1,6 +1,6 @@
# Detect and remove unwanted software installed by peripherals in Fleet
When you plug in a monitor, docking station, or printer, Windows can quietly install companion software from monitor utilities that push trialware ads to docking station managers nobody asked for. The recent LG Monitor App story (July 2026) made headlines, but the broader problem is older: peripherals bundling junkware that lands on corporate workstations.
When you plug in a monitor, docking station, or printer, Windows can quietly install companion software, from monitor utilities that push trialware ads to docking station managers nobody asked for. The recent LG Monitor App story (July 2026) made headlines, but the broader problem is older: peripherals bundling junkware that lands on corporate workstations.
This guide walks through detecting and automatically removing unwanted software using Fleet policies and scripts. It covers traditional Windows installers (MSI/EXE) and Store-installed MSIX apps like the LG Monitor App.
@@ -8,10 +8,10 @@ This guide walks through detecting and automatically removing unwanted software
Check these before you start:
- **Fleet Premium** for policy automation scripts. Script automations (triggering remediation when a policy fails) require Fleet Premium. Fleet Free users can create detection policies and run scripts manually from the UI.
- **Fleet Premium** for policy automation scripts. Script automations (triggering remediation when a policy fails) require Fleet Premium. Fleet Free users can create detection policies and run scripts manually from the Fleet UI.
- **Windows hosts enrolled in Fleet.** Detection uses the `programs` table for both MSI/EXE software and Store (MSIX) apps.
- **osquery 5.22.1 or later** for full Store app visibility. osquery 5.17.0 added MSIX packages to the `programs` table, and 5.22.1 fixed a gap where provisioned or never-launched Store apps were invisible. Check your agents with a live query: `SELECT version FROM osquery_info;`.
- **Scripts enabled.** If you use Fleet's MDM features, scripts are enabled by default. If you deploy fleetd without MDM, pass the `--enable-scripts` flag during installation.
- **Fleet's agent updated for full Store app visibility.** Store (MSIX) apps appear in the `programs` table once the agent is recent enough. Support for MSIX packages arrived in osquery 5.17.0, and osquery 5.22.1 fixed a gap where provisioned or never-launched Store apps were invisible. Check the version on your hosts with a live query: `SELECT version FROM osquery_info;`. Update Fleet's agent (`fleetd`) if a host reports a version below 5.22.1.
- **Scripts enabled.** If you use Fleet's MDM features, scripts are enabled by default. If you deploy `fleetd` without MDM, pass the `--enable-scripts` flag during installation.
## Create a policy to detect unwanted software (MSI/EXE)
@@ -29,9 +29,9 @@ SELECT 1 WHERE NOT EXISTS (
4. In the **Resolution** field, add instructions for your help desk: "McAfee trial software was detected and has been automatically removed. Contact IT if you were expecting to use McAfee products on this machine."
This query returns a row (pass) when no McAfee software exists. When McAfee is found, the subquery returns results, `NOT EXISTS` evaluates to false, and the outer query returns zero rows the policy fails and triggers any attached automation.
This query returns a row (pass) when no McAfee software exists. When McAfee is found, the subquery returns results, `NOT EXISTS` evaluates to false, and the outer query returns zero rows, so the policy fails and triggers any attached automation.
> **Note:** The `programs` table reads the Windows Uninstall registry keys (both MSI and EXE installers that register in Add/Remove Programs). On osquery 5.17.0 and later it also includes Store (MSIX) apps — see the Store apps section below for how to target those precisely.
> **Note:** The `programs` table reads the Windows Uninstall registry keys (both MSI and EXE installers that register in Add/Remove Programs). Recent versions of Fleet's agent also include Store (MSIX) apps in this table. See the Store apps section below for how to target those precisely.
## Create a script to remove unwanted software
@@ -112,7 +112,7 @@ foreach ($basePath in $uninstallPaths) {
return
}
# Quiet uninstall string (preferred already includes silent flags)
# Quiet uninstall string (preferred, already includes silent flags)
if ($entry.QuietUninstallString) {
Write-Output "Uninstalling $name (quiet)"
$code = Invoke-Uninstaller -CommandLine $entry.QuietUninstallString
@@ -123,9 +123,9 @@ foreach ($basePath in $uninstallPaths) {
return
}
# Standard uninstall string — caveat: may not support silent mode
# Standard uninstall string. Caveat: may not support silent mode
if ($entry.UninstallString) {
Write-Output "Uninstalling $name (standard may not be silent)"
Write-Output "Uninstalling $name (standard, may not be silent)"
$code = Invoke-Uninstaller -CommandLine $entry.UninstallString -ExtraArgs "/quiet /norestart"
if ($code -notin $successCodes) {
Write-Output "Failed: exit code $code"
@@ -150,7 +150,7 @@ Write-Output "McAfee removal complete."
exit 0
```
This script reads both the 64-bit and 32-bit Uninstall registry hives, rebuilds MSI uninstall commands from the product GUID (handling both `/I` and `/X` registrations, quoted or unquoted msiexec paths), and parses EXE uninstall strings into executable-plus-arguments so paths with spaces like anything under `Program Files` launch correctly. It prefers `QuietUninstallString` when available and falls back to the standard `UninstallString` with appended quiet flags. Exit codes 3010 and 1641 (success, reboot required) are treated as success so a completed uninstall doesn't trigger a spurious retry. The script returns non-zero only on real failures, so Fleet's 3-retry mechanism triggers correctly.
This script reads both the 64-bit and 32-bit Uninstall registry hives, rebuilds MSI uninstall commands from the product GUID (handling both `/I` and `/X` registrations, quoted or unquoted msiexec paths), and parses EXE uninstall strings into executable-plus-arguments so paths with spaces, like anything under `Program Files`, launch correctly. It prefers `QuietUninstallString` when available and falls back to the standard `UninstallString` with appended quiet flags. Exit codes 3010 and 1641 (success, reboot required) are treated as success so a completed uninstall doesn't trigger a spurious retry. The script returns non-zero only on real failures, so Fleet's 3-retry mechanism triggers correctly.
4. Click **Save** to create the script.
@@ -158,7 +158,7 @@ This script reads both the 64-bit and 32-bit Uninstall registry hives, rebuilds
> **Note:** If your organization legitimately uses McAfee/Trellix Endpoint Security on some machines, narrow the scope. Replace `*McAfee*` with `*McAfee Trial*` or `*McAfee Safe Search*` to avoid removing production security software.
> **Caveat:** Appending `/quiet /norestart` to arbitrary EXE uninstallers doesn't always work NSIS installers want `/S`, Inno Setup wants `/VERYSILENT`. If an uninstaller lacks quiet support, it will prompt for UI and hang in Fleet's non-interactive SYSTEM context until the script timeout. For stubborn software, use vendor-specific removal tools (e.g., McAfee's MCPR tool) deployed as a Fleet software package.
> **Note:** Appending `/quiet /norestart` to arbitrary EXE uninstallers doesn't always work. NSIS installers want `/S`, Inno Setup wants `/VERYSILENT`. If an uninstaller lacks quiet support, it will prompt for UI and hang in Fleet's non-interactive SYSTEM context until the script timeout. For stubborn software, use vendor-specific removal tools (for example, McAfee's MCPR tool) deployed as a Fleet software package.
## Connect the policy and script with automation
@@ -171,9 +171,9 @@ When any Windows host fails the McAfee policy, Fleet runs the uninstall script.
> **Note:** Policy automations attach to policies scoped to a specific fleet (team), not global policies. If you organize hosts by fleet, create the policy at that level and attach the script there.
## Detecting Store apps (MSIX) like the LG Monitor App
## Detect Store apps (MSIX) like the LG Monitor App
Companion apps like the LG Monitor App and Alienware Command Center install as MSIX packages from the Microsoft Store no user action required. Since osquery 5.17.0, MSIX packages appear in the `programs` table with a populated `package_family_name` column, and osquery 5.22.1 closed the remaining gap where apps that no user had launched were missing from inventory. That means the same policy pattern works here match on the package family name rather than the display name, since it's the stable identifier:
Companion apps like the LG Monitor App and Alienware Command Center install as MSIX packages from the Microsoft Store, with no user action required. Once Fleet's agent is recent enough, MSIX packages appear in the `programs` table with a populated `package_family_name` column. Support arrived in osquery 5.17.0, and osquery 5.22.1 closed the remaining gap where apps that no user had launched were missing from inventory. The same policy pattern works here: match on the package family name rather than the display name, since it's the stable identifier.
### Policy
@@ -190,7 +190,7 @@ SELECT 1 WHERE NOT EXISTS (
This returns zero rows (fail) when the LG Monitor App package is present on the host.
> **Note:** To find the package family name for any Store app, run `Get-AppxPackage -AllUsers | Select Name, PackageFamilyName` on an affected host, or query `SELECT name, package_family_name FROM programs WHERE package_family_name != ''` via Fleet live query. Vendors sometimes ship a separate installer stub package alongside the app itself — check for related packages (for example, names containing "Installer") and widen the `LIKE` pattern if you find one.
> **Note:** To find the package family name for any Store app, run `Get-AppxPackage -AllUsers | Select Name, PackageFamilyName` on an affected host, or query `SELECT name, package_family_name FROM programs WHERE package_family_name != ''` via Fleet live query. Vendors sometimes ship a separate installer stub package alongside the app itself. Check for related packages (for example, names containing "Installer") and widen the `LIKE` pattern if you find one.
> **Note:** Querying MSIX data in `programs` involves enumerating installed packages through the Windows Appx APIs, which is slower than the registry reads used for MSI/EXE entries. Policy evaluations run on a schedule (default hourly), so this doesn't affect end users, but live queries can take longer on hosts with many Store apps.
@@ -267,8 +267,8 @@ foreach ($drv in $lgDrivers) {
# Belt and suspenders: block device metadata retrieval, which is one of the
# channels Windows uses to deliver companion apps for connected hardware.
# Note: this does NOT block installs triggered by driver-store packages
# that's what the pnputil cleanup above is for.
# Note: this does NOT block installs triggered by driver-store packages.
# That's what the pnputil cleanup above is for.
$policyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata"
if (-not (Test-Path $policyPath)) {
New-Item -Path $policyPath -Force | Out-Null
@@ -287,11 +287,11 @@ exit 0
4. Save the script and attach it to the Store app policy via **Manage automations**.
> **Warning:** Before deploying, run `pnputil /enum-drivers` on an affected host and confirm the LG delivery packages' provider name and class. Adjust the `ProviderName` filter if your hosts report a different string. Only `SoftwareComponent` and `Extension` class packages are targeted display drivers are untouched.
> **Warning:** Before deploying, run `pnputil /enum-drivers` on an affected host and confirm the LG delivery packages' provider name and class. Adjust the `ProviderName` filter if your hosts report a different string. Only `SoftwareComponent` and `Extension` class packages are targeted, so display drivers are untouched.
> **Warning:** Disabling device metadata retrieval blocks companion app installation via device metadata for ALL hardware including legitimate ones your users may want. Scope this to specific fleets rather than applying it globally.
> **Warning:** Disabling device metadata retrieval blocks companion app installation via device metadata for ALL hardware, including legitimate ones your users may want. Scope this to specific fleets rather than applying it globally.
> **Note:** `PreventDeviceMetadataFromNetwork` is also settable through Windows MDM as an ADMX-backed policy (`./Device/Vendor/MSFT/Policy/Config/DeviceInstallation/PreventDeviceMetadataFromNetwork`). If you manage Windows hosts with Fleet MDM, a custom configuration profile is the more durable option profiles are re-enforced, while a script sets the value once. The script approach above works on hosts without MDM enrollment.
> **Note:** `PreventDeviceMetadataFromNetwork` is also settable through Windows MDM as an ADMX-backed policy (`./Device/Vendor/MSFT/Policy/Config/DeviceInstallation/PreventDeviceMetadataFromNetwork`). If you manage Windows hosts with Fleet MDM, a custom configuration profile is the more durable option: profiles are re-enforced, while a script sets the value once. The script approach above works on hosts without MDM enrollment.
## Get notified when unwanted software is detected
@@ -308,9 +308,9 @@ The same pattern works for any unwanted software:
- **Docking station utilities.** DisplayLink Manager, Plugable utilities, and other dock software show up in `programs`. Use the policy template and scope by `publisher` plus a specific product name.
- **Printer bundles.** Canon, Epson, and Brother utilities follow the same approach. Scope by publisher to avoid hitting unrelated software.
- **Monitor companion apps (Store).** Alienware Command Center auto-installs via the same mechanisms. Use the same `package_family_name LIKE '...'` pattern — run `Get-AppxPackage -AllUsers | Select PackageFamilyName` on an affected host to get the exact prefix.
- **Monitor companion apps (Store).** Alienware Command Center auto-installs via the same mechanisms. Use the same `package_family_name LIKE '...'` pattern. Run `Get-AppxPackage -AllUsers | Select PackageFamilyName` on an affected host to get the exact prefix.
> **Note:** Avoid broad substring matches like `%HP%` in the `name` field they hit unrelated programs. Scope on `publisher` or use specific product names.
> **Note:** Avoid broad substring matches like `%HP%` in the `name` field, since they hit unrelated programs. Scope on `publisher` or use specific product names.
## Verify the cleanup worked
@@ -333,15 +333,15 @@ Automations fire on transition (newly failing: no-response-to-fail or pass-to-fa
**Store app doesn't appear in the software inventory.**
Check the host's osquery version (`SELECT version FROM osquery_info;`). MSIX support in the `programs` table requires osquery 5.17.0, and apps that no user has launched the normal state for auto-installed companion apps require 5.22.1. On older agents, update the fleetd package to bring Store apps into inventory.
Check the version reported on the host (`SELECT version FROM osquery_info;`). MSIX support in the `programs` table requires osquery 5.17.0, and apps that no user has launched, the normal state for auto-installed companion apps, require 5.22.1. On older versions, update Fleet's agent (`fleetd`) to bring Store apps into inventory.
**Script hangs or times out on some hosts.**
If an uninstaller lacks quiet/silent flags, it may prompt for UI input which fails in Fleet's non-interactive SYSTEM context and hangs until the script timeout. The timeout is an agent option (`script_execution_timeout` under `agent_options`, default 300 seconds, maximum 18000), settable through the UI or GitOps. For stubborn software, use vendor-specific removal tools deployed as Fleet software packages.
If an uninstaller lacks quiet/silent flags, it may prompt for UI input, which fails in Fleet's non-interactive SYSTEM context and hangs until the script timeout. The timeout is an agent option (`script_execution_timeout` under `agent_options`, default 300 seconds, maximum 18000), settable through the Fleet UI or GitOps. For stubborn software, use vendor-specific removal tools deployed as Fleet software packages.
**Store app keeps reinstalling after removal.**
Windows has two delivery channels that can re-trigger the install when the user reconnects the peripheral. The first is device metadata: Windows matches the hardware to a companion app listing and installs it. The `PreventDeviceMetadataFromNetwork` policy blocks this channel. The second is driver-store delivery: the vendor ships a `SoftwareComponent` driver package (via Windows Update, matched to hardware IDs) whose only job is to install the Store app. The metadata policy does NOT block this channel — the driver package must be removed from the driver store with `pnputil`, which the removal script above does. If the app still returns, check `pnputil /enum-drivers` output for vendor packages the script's filter missed, and check whether Windows Update re-delivered the driver package (block it with a driver group policy or WSUS/WUfB deferral if so).
Windows has two delivery channels that can re-trigger the install when the user reconnects the peripheral. The first is device metadata: Windows matches the hardware to a companion app listing and installs it. The `PreventDeviceMetadataFromNetwork` policy blocks this channel. The second is driver-store delivery: the vendor ships a `SoftwareComponent` driver package (via Windows Update, matched to hardware IDs) whose only job is to install the Store app. The metadata policy does NOT block this channel. The driver package must be removed from the driver store with `pnputil`, which the removal script above does. If the app still returns, check `pnputil /enum-drivers` output for vendor packages the script's filter missed, and check whether Windows Update re-delivered the driver package (block it with a driver group policy or WSUS/WUfB deferral if so).
**Automation retry limit reached.**
@@ -349,9 +349,9 @@ Script automations attempt up to 3 times, retriggering on non-zero exit codes. I
## Further reading
- [Policy automations](https://fleetdm.com/guides/automations) Configure webhooks and script triggers for policies.
- [Run scripts on policy failure](https://fleetdm.com/guides/policy-automation-run-script) Step-by-step for connecting policies to remediation scripts.
- [Provisioned MSIX apps in software inventory (fleetdm/fleet#39065)](https://github.com/fleetdm/fleet/issues/39065) Background on the osquery 5.22.1 fix that makes never-launched Store apps visible in `programs`.
- [Policy automations](https://fleetdm.com/guides/automations). Configure webhooks and script triggers for policies.
- [Run scripts on policy failure](https://fleetdm.com/guides/policy-automation-run-script). Step-by-step for connecting policies to remediation scripts.
- [Provisioned MSIX apps in software inventory (fleetdm/fleet#39065)](https://github.com/fleetdm/fleet/issues/39065). Background on the osquery 5.22.1 fix that makes never-launched Store apps visible in `programs`.
<meta name="articleTitle" value="Detect and remove unwanted software installed by peripherals in Fleet">
<meta name="authorFullName" value="Dhruv Majumdar">