Skip to content

Capture Windows FMA process names for the "app open" query - #50827

Closed
allenhouchins wants to merge 1 commit into
mainfrom
allenhouchins/windows-fma-process-name-4be10f
Closed

Capture Windows FMA process names for the "app open" query#50827
allenhouchins wants to merge 1 commit into
mainfrom
allenhouchins/windows-fma-process-name-4be10f

Conversation

@allenhouchins

Copy link
Copy Markdown
Member

Checklist for submitter

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See 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.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Details

Important

This supersedes #50825 — that PR touched the same function, so it was folded in here rather than merged separately. Please close #50825 rather than merging both.

The problem

The Windows "app open" pre-install query (FMAQueries.Open) is the gate that holds a patch back until the user closes the app. It had no per-app source of truth for the process name: it came from the hard-coded windowsOpenQueryOverrides map in pkg/patch_policy/patch_policy.go, keyed by catalog name, which covers 51 of 434 winget inputs. Everything else fell back to guessing <lowercased name>.exe.

A wrong process name fails green. NOT EXISTS over a predicate that never matches is always true, so the app always reads as "closed" and Fleet patches over a running app — no error, anywhere. 183 of the uncovered apps have a space in the name ("AWS Client VPN"aws client vpn.exe), where the guess cannot be right.

Editing a shared map in pkg/patch_policy is also the wrong place for it: the key is the catalog name, which macOS and Windows FMAs deliberately share to group in the library.

The change

A process_names array on the winget input JSON, so the names are captured from the installer at authoring time next to the other identity fields:

{
  "name": "7-zip",
  "unique_identifier": "7-Zip",
  "process_names": ["7zFM.exe", "7zG.exe"]
}
  • Validated at ingest via patch_policy.ValidateProcessNames — each entry must be a bare file name ending in .exe, or in * for a prefix match. Malformed entries hard-error rather than being best-effort corrected, because the runtime symptom is invisible.
  • One name emits = 'x.exe', several collapse to IN (...), a trailing * becomes LIKE 'x%' (for apps that run many differently-named helpers, e.g. 1password*), and mixing the two yields a parenthesized OR.
  • Precedence: process_nameswindowsOpenQueryOverrides<name>.exe guess. Apps can migrate off the map without touching it.

Folded in from #50825: a multi-word catalog name with no process_names and no override now emits no open query at all rather than an unmatchable guess, and Queries.Open is omitempty. The two changes compose — process_names is what gives a multi-word app a working gate in the first place.

macOS is unaffected and passes nil. Its open query joins apps.path to processes.path, so the bundle identifier already resolves the running process. Windows has no equivalent because programs.install_location is unreliable, especially for MSI installers.

Documentation

The new-fma skill and the contributor README now document how to read process names out of each installer type, verified against real installers rather than assumed:

Type Count Tool Result
msi 170 msiinfo export … Shortcut / File Works — Shortcut.Target ([#_7zFM.exe]) is the user-facing exe
msix 7 unzip -p app.msix AppxManifest.xml Works — it's a zip; Executable="app\Slack.exe"
exe / nullsoft part of 251 7zz l app.exe Works — Notepad++ → notepad++.exe, updater/GUP.exe
exe / inno part of 251 innoextract -l -m Only through Inno 6.0.5; 1.9 (current Homebrew) fails on 6.3. 7zz can't read Inno either
exe / burn Not readable offline (nested MSI chain)

When the installer won't open, the guidance is to ship without process_names and read the exe off the validator log — it installs on a real Windows host, where ARP DisplayIcon and InstallLocation are visible — rather than guessing.

Testing

Unit tests cover the predicate shapes, escaping, dedupe/trim, precedence over the map, the multi-word drop, and the interaction between the two: "XnSoft XnConvert" gets a working query with process_names and nothing without it.

Manually verified by regenerating one app on each side of the new branch point:

  • 7-zip (single word, has process_names) → IN ('7zfm.exe','7zg.exe'), byte-identical to the curated map entry it replaces
  • 010 Editor (multi-word, neither) → no open key, zero-line diff via omitempty

go test passes for pkg/patch_policy, both ingesters, and server/mdm/maintainedapps (which consumes Queries.Open and is unaffected by the empty value). gofmt, go vet, full go build ./..., and GOOS=windows go build ./cmd/maintained-apps/validate/ are clean.

Note

make lint-go-incremental could not be run locally — building the custom golangci-lint binary requires cloning from GitHub, which the sandbox blocks. Relying on CI for the lint pass.

Notes for reviewers

  • GenerateOpenQuery gained a fourth parameter; both call sites and the tests are updated.
  • No existing outputs change except 7-zip. No committed output currently has an open key, so the rest pick these up on the next scheduled ingest.
  • The 51 curated map entries are untouched and still work. Migrating them into process_names is a reasonable follow-up but would regenerate 51 outputs, so it is deliberately not in this PR.

The Windows "app open" pre-install query (FMAQueries.Open) that gates
patching a running app had no per-app source of truth for the process
name. It came from a hard-coded windowsOpenQueryOverrides map keyed by
catalog name (51 of 434 winget inputs), falling back to guessing
"<lowercased name>.exe" for everything else.

A wrong process name fails green: NOT EXISTS over a predicate that never
matches is always true, so the app always reads as "closed" and Fleet
patches over a running app with no error anywhere.

Add a process_names array to the winget input JSON, so the names can be
captured from the installer at authoring time alongside the other
identity fields, instead of requiring an edit to a shared map in another
package. Entries are validated at ingest (must be a bare file name
ending in .exe, or in "*" for a prefix match) rather than best-effort
corrected, since a malformed name is invisible at runtime. Multiple
exact names collapse to IN (...); a prefix entry becomes LIKE; mixing
the two yields a parenthesized OR. Precedence is process_names, then the
override map, then the guess.

This also folds in #50825, which touches the same function: a multi-word
catalog name with no process_names and no override now emits no open
query at all rather than an unmatchable guess, and Open is omitempty.
That PR is superseded by this one. The two compose so that
process_names is what gives a multi-word app a working gate.

macOS needs none of this and passes nil: its open query joins apps.path
to processes.path, so the bundle identifier already resolves the running
process. Windows has no equivalent because programs.install_location is
unreliable, especially for MSI installers.

7-zip is included as a worked example, with the two process names read
out of its MSI File table. It regenerates byte-identically to its
previous curated map entry.

The new-fma skill and the contributor README document how to read
process names out of each installer type (msiinfo for MSI, AppxManifest
for MSIX, 7zz for NSIS), which tools fail and where (innoextract only
supports through Inno 6.0.5), and to fall back to the validator log
rather than guessing.
@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.49123% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.54%. Comparing base (bc537a3) to head (efc999c).

Files with missing lines Patch % Lines
ee/maintained-apps/ingesters/winget/ingester.go 33.33% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #50827   +/-   ##
=======================================
  Coverage   68.53%   68.54%           
=======================================
  Files        3977     3977           
  Lines      256168   256216   +48     
  Branches    13818    13818           
=======================================
+ Hits       175566   175623   +57     
+ Misses      64982    64978    -4     
+ Partials    15620    15615    -5     
Flag Coverage Δ
backend 69.64% <96.49%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@allenhouchins

Copy link
Copy Markdown
Member Author

Closing in favor of a combined branch: these changes are being kept together with the Windows open-query audit (which will populate process_names across the catalog), so one PR carries the mechanism and the data. The commit is preserved as-is on the new branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant