236 Commits
Author SHA1 Message Date
Allen Houchins 051d12718c Add JetBrains ReSharper as a Windows Fleet-maintained app (#50659)
**Related issue:** Resolves #50567

Adds JetBrains ReSharper as a Windows Fleet-maintained app (winget
`JetBrains.ReSharper`, version `2026.2.0.2`).

ReSharper is a Visual Studio extension rather than a standalone app, so
it does not follow the pattern of the other JetBrains FMAs (Rider,
PhpStorm, DataGrip, etc.), which are plain NSIS installers that take
`/S`. Reviewers should read the risks below before approving — a couple
of things can only be confirmed from a validator run.

## What's here

- `ee/maintained-apps/inputs/winget/resharper.json`
- `ee/maintained-apps/inputs/winget/scripts/resharper_install.ps1` /
`resharper_uninstall.ps1`
- Generated `ee/maintained-apps/outputs/resharper/windows.json` +
`apps.json` entry
- Icon (`Resharper.tsx`, website PNG, alphabetical `index.ts` entries),
generated from JetBrains' own brand asset

## Decisions that differ from the other JetBrains FMAs

**`use_display_version_for_patch` is omitted.** Every other JetBrains
winget input sets it, but the ReSharper manifest has no
`AppsAndFeaturesEntries`, so the ingester hard-errors with
`use_display_version_for_patch is set but no DisplayVersion found in
winget manifest`. The patch policy therefore compares against winget's
`2026.2.0.2`.

**Custom `exists_query` instead of `fuzzy_match_name`.** The prefix is
loose enough to match a possible per-VS-instance suffix, and excludes
the separate ReSharper C++ and ReSharper SDK products:

```sql
SELECT 1 FROM programs WHERE name LIKE 'JetBrains ReSharper%' AND name NOT LIKE 'JetBrains ReSharper C++%' AND name NOT LIKE 'JetBrains ReSharper SDK%' AND publisher = 'JetBrains s.r.o.';
```

**Install script detects Visual Studio.** It builds `/VsVersion` from
the instances `vswhere` reports and runs the installer with
`/Silent=True /PerMachine=True /SkipEtwService=True`:

- `/PerMachine=True` — the installer otherwise targets `%LocalAppData%`,
which under Fleet's SYSTEM context would land in the SYSTEM profile
instead of the developer's. The path is not configurable
([RSRP-428991](https://youtrack.jetbrains.com/issue/RSRP-428991)).
- `/SkipEtwService=True` — JetBrains documents that `EtwHostService.msi`
always raises a UAC prompt, so a fully silent install of every component
is not possible
([SUPPORT-A-3189](https://youtrack.jetbrains.com/articles/SUPPORT-A-3189)).
- It then waits for the uninstall registry entry (what osquery reads),
because the web bootstrapper can outlive its own exit code, and logs the
resulting ARP entries.

**Uninstall removes every matching entry**, since ReSharper registers
one per Visual Studio instance, using the defensive `UninstallString`
parser and appending `/Silent=True` rather than the NSIS `/S`.

Switches come from [JetBrains' silent install/uninstall
article](https://resharper-support.jetbrains.com/hc/en-us/articles/207241485-How-to-use-silent-install-and-silent-uninstall-of-ReSharper-via-Command-Line),
not guesswork.

## `unique_identifier` is provisional

`program_publisher` is verified — `JetBrains s.r.o.` is hard-coded next
to the ARP value names (`DisplayName`, `DisplayVersion`,
`UninstallString`, `Publisher`) in `JetBrains.Platform.Installer.exe`,
extracted from the installer.

The **DisplayName is not verifiable offline.** The winget URL is a
two-stage web bootstrapper: the 69 MB `.web.exe` contains
`JetBrains.Platform.Installer.Bootstrap.exe`, which downloads the
JetBrains dotUltimate installer, which downloads the product packages.
The ARP entry is written by that downloaded stage under
`Software\Microsoft\Windows\CurrentVersion\Uninstall\{GUID}`, with
`DisplayName` taken from a per-VS-host `PresentableName`. So `JetBrains
ReSharper` is a best-supported guess.

It can be confirmed from a validator run:
`cmd/maintained-apps/validate/windows.go` searches `programs` with a
loose `LOWER(name) LIKE '%…%'` on both the catalog name and
`unique_identifier`, and logs `Found app: '<DisplayName>' … Version:
<ver>` after running `MutateSoftwareOnIngestion`. That reveals both the
true DisplayName and the post-mutation version. The install script
prints the same information. **Expect a follow-up commit correcting
`unique_identifier` (and possibly the exists query) once that log
lands.**

## Risks

1. **Payload is not pinned.** The SHA covers only the 69 MB
bootstrapper; roughly 1.7 GB is fetched from `download.jetbrains.com` at
install time. JetBrains publishes only a `windowsWeb` download for
ReSharper, so there is no offline installer to point at. Install
duration may exceed script timeouts.
2. **Requires Visual Studio.** With no VS present the installer has
nothing to install, so the script exits 1 with a clear message.
`windows-latest` runners ship Visual Studio 2022 Enterprise, so
validation should be able to install.
3. **`/PerMachine=True` conflicts with pre-existing per-user installs.**
JetBrains states machine-wide mode "is not compatible with existing
installations in user profiles"; one must be removed first.
4. **`/SkipEtwService=True` omits the ETW host service**, so
dotTrace/dotMemory profiling integration is incomplete. This is the
documented tradeoff for an unattended install.
5. **Version reconciliation unconfirmed.** If the DisplayName ends in a
marketing version, the JetBrains name-based version mutation fires and
the validator's prefix check passes; a VS-suffixed name would instead
fall back to the registry `DisplayVersion`.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] `go test ./ee/maintained-apps/...` passes; both output JSON files
parse; generated SHA matches the winget manifest.
- [ ] QA'd all new/changed functionality manually — **not done.**
Install/uninstall need a Windows host with Visual Studio; relying on the
FMA Windows validator, which is also how `unique_identifier` gets
confirmed.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added ReSharper to the maintained Windows applications catalog.
* Added support for silent machine-wide installation and uninstallation.
* Added Visual Studio compatibility checks and installation failure
reporting.
  * Added a ReSharper icon for display in the software catalog.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-07 12:16:17 -05:00
616f9ab108 Update scripts in Fleet-maintained apps (#50756)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Updates**
- Refreshed macOS and Windows installer metadata for numerous maintained
applications, including AltTab, Arc, Calibre, ChatGPT, Chrome, Kiro,
Loom, Postman, Prisma Access Browser, RustRover, and others.
- Updated release versions, download links, version detection, and
integrity checks.

- **Bug Fixes**
  - Improved Evernote removal verification on Windows.
- Enhanced Krita and Proton Drive macOS cleanup, including related
support files and background services.

- **Configuration**
  - Webex is now marked as frozen.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-07 12:15:59 -05:00
kitzyandAllen Houchins 5db78a63e3 Add Visual Studio 2022 (Community/Professional/Enterprise) as Windows FMAs (#50717)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #50653

Adds **Visual Studio 2022 Community, Professional, and Enterprise** as
Windows Fleet-maintained apps. customer-universitas needs all three
editions.

## What's here

- Three input files, one per edition, each pointing at its own winget
package
(`Microsoft.VisualStudio.2022.{Community,Professional,Enterprise}`, all
at `17.14.37`).
- A shared install script (`visual_studio_2022_install.ps1`) — the
downloaded file is a ~4 MB bootstrapper, not the IDE. The real multi-GB
payload downloads from Microsoft *during* the install script, so install
time depends on the host's network speed and counts against Fleet's
1-hour software-install timeout. `--wait` is required or the
bootstrapper forks the real install to a background process and returns
almost immediately.
- Three uninstall scripts (one per edition) that resolve the install
path via `vswhere.exe -products
Microsoft.VisualStudio.Product.<Edition>` and call `vs_installer.exe
uninstall --installPath <path> --quiet --norestart --wait`, since VS has
no normal `UninstallString`.
- Both scripts map winget's documented `3010`/`1641` (reboot
pending/initiated) to a successful exit, and fail clearly on
`1001`/`1618` (another VS Installer operation already running).
- Default install ships the bare IDE shell (no `--add` workloads) —
matches plain `winget install` behavior, per the issue's own conclusion
that this needs no special-casing.
- Icons: no scriptable source (no Windows host to extract the real
per-edition `.exe` icon, and Microsoft's own download pages don't expose
one) turned up distinct Community/Professional/Enterprise badge art, so
all three currently use the same public Visual Studio mark ([Wikimedia
Commons](https://commons.wikimedia.org/wiki/File:Visual_Studio_Icon_2022.svg),
marked public domain). **Flagging for #g-software Product Designer** to
swap in the real per-edition badges if we have them.

## What I could not verify (no Windows host in this environment)

- `unique_identifier`/publisher (`Visual Studio
Community/Professional/Enterprise 2022`, publisher `Microsoft
Corporation`) are taken from the winget locale manifest, not confirmed
against a live registry entry.
- The version-string quirk the issue calls out: winget's
`AppsAndFeaturesEntries.DisplayVersion` is `"17.14.37 (July 2026)"`, not
a clean version. I deliberately did **not** set
`use_display_version_for_patch` — feeding that non-numeric string in as
the patch target would break `version_compare` ordering across future
version bumps (see the comment in `ingester.go`). Instead the patch
policy compares against the plain winget `PackageVersion` (`17.14.37`),
same as most winget FMAs. This should hold up if `version_compare` reads
leading numeric-dot segments and ignores the trailing text, but I can't
confirm that against real `programs.version` output without a host.
- Whether `vs_installer.exe` actually honors `--wait` for `uninstall`
the way the bootstrapper does for `install` — Microsoft's own docs say
`--wait` "can only be passed into the bootstrapper; the installer
(setup.exe) doesn't support it," which is in tension with the exact
command this issue asked for and what I've seen used in the wild. Worth
watching in validation logs.
- End-to-end install timing on a normal (non-datacenter) connection,
within the 1-hour timeout.

# Checklist for submitter

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
<!-- Not added — no precedent for a changes file on FMA-addition PRs
(e.g. #50553, TeamViewer Host). -->

- [ ] 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

- [x] `apps.json` is valid JSON with descriptions filled in for all
three editions
- [x] Generator output reviewed: exists/patched queries, SHA256 (matches
the live winget manifest), installer URLs
- [x] `go build`/`go test ./ee/maintained-apps/...` pass; no shared
ingester/validator code changed
- [ ] FMA validator: install → detect → uninstall on a Windows host —
**pending, needs a Windows host**
- [ ] QA'd all new/changed functionality manually — **pending, same
reason**

## FMA-specific (from issue #50653's acceptance criteria)

- [x] Edition scope decided and recorded on the issue (all three:
Community, Professional, Enterprise)
- [x] Input added under `ee/maintained-apps/inputs/winget/`
- [x] Custom install script handles `3010`/`1641` as success and fails
clearly on `1618`/`1001`
- [x] Custom uninstall script resolves the install path via `vswhere`
and calls `vs_installer.exe uninstall`
- [ ] Identity fields verified against a real installed host — **not
yet, see above**
- [ ] Patch policy verified against actual `programs.version` — **not
yet, see above**
- [ ] Install verified end to end within the 1-hour timeout on a
normal-speed connection — **not yet**
- [ ] Passes the FMA validator: install → detect → uninstall — **not
yet**
- [x] Icon exists (shared placeholder mark across all three editions —
flagged for PD)

No shared/ingester/validator code changed.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added Visual Studio 2022 Community, Professional, and Enterprise
editions to the software catalog.
- Added support for installing and uninstalling each edition with quiet
execution, installation detection, error handling, and reboot handling.
  - Added version 17.14.37 metadata and update detection.
- Added Visual Studio branding and edition-specific icons throughout the
software interface.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-07 10:59:27 -05:00
Allen Houchins 01f9e534f7 Fix Genesys Cloud FMA: winget dropped x86, ship x64 MSI (#50742)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->

# Checklist for submitter

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

- [x] 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

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

# Details

The nightly maintained-apps ingestion job panicked with `failed to find
installer for app` on Genesys Cloud:

```
{"time":"2026-08-07T03:17:22.787715966Z","level":"INFO","msg":"ingesting winget app","name":"Genesys Cloud"}
panic: ingesting winget app: failed to find installer for app
```

## Why

Genesys.GenesysCloud **2.53.923.0** stopped publishing x86 installers
upstream. Previous versions (e.g. 2.51.916.0) shipped two x86 installers
(a burn `.exe` and a wix `.msi`); the latest manifest ships only a
single **x64** wix MSI. Our input pinned `installer_arch: "x86"`, so the
ingester filtered out the only available installer and panicked.

## What changed

- `ee/maintained-apps/inputs/winget/genesys-cloud.json`:
`installer_arch` `x86` → `x64`
- `ee/maintained-apps/outputs/genesys-cloud/windows.json`: regenerated
with `go run ./cmd/maintained-apps -slug genesys-cloud/windows` —
version 2.51.916.0 → 2.53.923.0, installer URL now the x64 MSI, sha256
matches the winget manifest's `InstallerSha256`

## Notes for reviewers

- Exists/patched queries are unchanged (still keyed on ARP `name =
'GenesysCloud'`, `publisher = 'Genesys Inc.'`), and install/uninstall
script refs are identical since it's still a machine-scope MSI —
detection and remediation carry over for existing installs.
- The MSI `UpgradeCode` is unchanged upstream
(`{A0E8C487-C337-441C-83AF-90364DA4B793}`), so the x64 MSI upgrades
existing x86 installs in place (ProductCode is new, install dir moves
from `ProgramFiles(x86)` to `ProgramFiles`).
- The new manifest declares a `Microsoft.VCRedist.2015+.x64` dependency
(the old x86 MSI declared the x86 variant). Fleet doesn't resolve winget
dependencies; the FMA validator run on this PR will confirm whether the
installer tolerates its absence.
2026-08-06 22:33:07 -05:00
ecdf1ff003 Add TeamViewer Host as a Windows Fleet-maintained app (#50553)
**Related issue:** Resolves #50332

Adds **TeamViewer Host** as a Windows Fleet-maintained app.

## The issue's premise was wrong — this needed no new capability

#50332 was blocked on "not in winget." It is in winget:
`TeamViewer.TeamViewer.Host`, published continuously since **June 2023**
(v15.42.8), currently **15.80.4** (merged upstream as
microsoft/winget-pkgs#409335, 2026-07-29). I downloaded the live x64
installer and its SHA256 matches that manifest byte-for-byte.

It reads as absent because it's nested a level deeper than you'd expect
— `manifests/t/TeamViewer/TeamViewer/Host/`, a sibling of the full
client's *version* directories rather than of the publisher's package
directories.

Consequence: this comes off the dependency on #50364, and that FR loses
one of its three examples. The other two (#50328, #50329, OLE DB Driver
18/19) still hold — there is no OLE DB package under any winget
publisher.

## Identity verified against the installer, not winget metadata

Per the `new-fma` golden rule, from the x64 MSI's `Property` and
`Registry` tables:

| Field | Value | Evidence |
|---|---|---|
| `unique_identifier` | `TeamViewer Host` | MSI `ProductName`; no
`ARPDISPLAYNAME`, empty `Registry` table, so this is the ARP DisplayName
|
| publisher | `TeamViewer` | MSI `Manufacturer`, equal to the winget
locale `Publisher` → no `program_publisher` override |
| `installer_scope` | `machine` | `ALLUSERS=1` |
| bootstrapper? | No | no `ARPSYSTEMCOMPONENT` |

Corroborated independently by [silentinstallhq's PSADT
script](https://silentinstallhq.com/teamviewer-host-install-and-uninstall-powershell/),
which detects the app with `Get-InstalledApplication -Name 'TeamViewer
Host'` and uses `/S` for both install and uninstall.

Generated exists query:

```sql
SELECT 1 FROM programs WHERE name = 'TeamViewer Host' AND publisher = 'TeamViewer';
```

No collision with the existing `teamviewer/windows` FMA, which generates
an exact `name = 'TeamViewer'`.

## Why exe + `ignore_hash`, matching the full client

winget offers Host as an NSIS exe and as a nested `wix` MSI inside a
zip. The ingester can't select the zip (`installer.InstallerType` is
`zip`, which never normalizes to `msi`), so the exe is the only
reachable installer.

TeamViewer publishes no version-pinned Host exe —
`TeamViewer_Host_Setup_x64_15.80.4.exe` and the x86 equivalent both 404
— so the manifest's URL is the unpinned `TeamViewer_Host_Setup_x64.exe`
and `ignore_hash: true` is required. This is vendor asymmetry, not a
fixable winget defect: the *full* client does publish pinned exe URLs.
Same reason `teamviewer/windows` already sets `ignore_hash`.

## Correcting the coexistence note in #50332

The issue assumed Host and the full client can co-exist. They can't. The
Host MSI's `LaunchCondition` table blocks the install outright:

> Error 25001: An incompatible TeamViewer package was detected that
conflicts with the current MSI package: TeamViewer_Full 64-bit. Please
manually uninstall this package.

…plus equivalents for Full 32-bit/ARM64, Host ARM64, and the NSIS
installs. **Relevant to validation: the validator host must not already
have `teamviewer/windows` installed.**

## Icons

No `index.ts` change needed — `matchLoosePrefixToKey` treats keys as
whole words at the start, so `"teamviewer host"` matches the existing
`teamviewer` key and inherits the TeamViewer brand icon. Verified
against the real 1,163-key map.

The website resolves its icon from the slug
(`app-icon-${slug}-60x60@2x.png`) with no such fallback, so
`app-icon-teamviewer-host-60x60@2x.png` is added — a copy of the
existing TeamViewer brand asset, since Host ships the same logo.

## Two things for review

1. **The PowerShell is unverified.** Authored on macOS with no
PowerShell available, so install/uninstall have not been executed. The
uninstall script searches ARP by DisplayName instead of a hardcoded key,
and uses the three-shape `UninstallString` parser (TeamViewer's is
unquoted and contains a space in `C:\Program Files\...`, which the older
`.Split('"')` approach in `teamviewer_uninstall.ps1` mishandles).
Validator run is the real check.
2. **Category mismatch with the macOS side.** This uses `Communication`,
matching the merged `teamviewer/windows`. #47121 adds
`teamviewer-host/darwin` with `Productivity`. Worth reconciling — and
that PR will want this same website PNG, so expect a trivial conflict.

`name` is `TeamViewer Host`, matching #47121 so both platforms group
together in the FMA library.

## Testing

- [x] `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...`
passes
- [x] Generator is idempotent — re-running produces no diff and
preserves the `apps.json` description
- [x] `apps.json` is valid JSON with no empty descriptions
- [x] Live installer SHA256 confirmed against the winget manifest
- [ ] FMA validator: install → detect → uninstall on a Windows host
**(pending — needs a host without the full TeamViewer client)**

No shared code changed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-05 14:04:49 -05:00
Allen Houchins bc3eee5f32 Re-add Dell Display and Peripheral Manager Windows FMA, validate on client-OS runner (#50313)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** NA (Windows FMA workstream; follow-up to #49127,
which dropped DDPM)

Re-adds **Dell Display and Peripheral Manager**
(`Dell.DisplayAndPeripheralManager` 2.2.2.8) as a Windows
Fleet-maintained app, and adds a `requires_client_os` routing override
so its CI validation always runs on the `windows-11-arm` runner.

## Why DDPM was dropped before, and why it's viable now

DDPM was dropped from the earlier re-add because its InstallShield setup
aborted with `0x80042000` under every documented silent switch, which
was diagnosed at the time as a .NET-prerequisite/headless-chaining
problem. A new debug run with Dell's own `/CreateDebugLog` switch shows
the real cause: the setup evaluates the OS at `OFUIBefore` and
terminates because the runner reports **Microsoft Windows Server 2025**.
DDPM is a Windows 10/11 client application and refuses to install on
Server SKUs — which is exactly what GitHub's x64 `windows-latest` image
is.

```
OSetUMode() 0 AP:2.2.2.8
OFUIBefore
Os Major10 Minor0
OS - 44444
// End Log File...
```

## `requires_client_os` CI routing

- New optional winget input field `requires_client_os: true` (documented
in `ee/maintained-apps/README.md` and on the Go input struct; ignored by
ingestion).
- `.github/scripts/partition-fma-apps.sh` routes any app with this flag
to `windows-11-arm` — the only GitHub-hosted client-OS Windows runner —
regardless of `installer_arch`. The x64 installer runs there under Prism
emulation; DDPM's gate is the OS SKU, not the architecture.
- Verified locally: partitioning the full 421-app Windows catalog
reroutes only `dell-display-and-peripheral-manager/windows`.

## App identity (verified against the real installer)

- Downloaded `DDPM-Setup_2.2.2.8.exe` from `dl.dell.com` (Chrome UA per
#49123); SHA256 matches the winget manifest.
- Embedded InstallShield `[Application]` block: `Name=Dell Display and
Peripheral Manager`, `Company=Dell Technologies`; ProductCode matches
the manifest GUID. The setup log reports `AP:2.2.2.8` as the registering
version.
- Installs with Dell's documented managed-deployment switches `/Silent
/HeadlessMode=true /TelemetryConsent=false /TurnOffCA` — the final
pre-drop iteration (6d0f2c00af), which also declines telemetry and
disables DDPM's self-updater on Fleet-managed hosts. Uninstalls via
`msiexec /x` on the ProductCode looked up in the registry by
DisplayName.

Input/uninstall script/icon are restored from the pre-drop state; the
install script is the final pre-drop iteration with its root-cause
comment corrected (Server-SKU OS gate, not headless-SYSTEM chaining).
Output regenerated (winget still at 2.2.2.8; script refs verified).

# Checklist for submitter

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

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] QA'd all new/changed functionality manually (partition script
exercised locally over the full catalog and a mixed PR-style slug list;
ingester regenerated with no output drift; `go test
./ee/maintained-apps/ingesters/winget/` passes)
- [ ] `test-fma-windows-pr-only` validates DDPM on the `windows-11-arm`
runner in this PR's CI


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Dell Display and Peripheral Manager to the Windows software
catalog, including installation, uninstallation, detection, metadata,
and an app icon.
* Added support for routing applications that require a Windows client
operating system to the appropriate Windows 11 ARM test environment.

* **Documentation**
* Documented Windows client operating system routing behavior and test
environment architecture details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-05 12:21:55 -05:00
33b4efe9c1 Update install script and Fleet-maintained apps (#50499)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Chores

* Updated numerous maintained application definitions with newer
releases, download links, version checks, and checksums across Windows
and macOS.
* Improved Windows installation reliability by recognizing successful
installations that require a restart.
* Corrected installer log-path handling for paths containing spaces.
* Refreshed metadata for applications including Firefox, Docker Desktop,
Discord, Tailscale, and many others.
* Adjusted Google Credential Provider validation settings to support its
installer distribution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-04 22:16:49 -05:00
Allen Houchins 4d6dbf8639 Fix Gpg4win FMA ingest panic after winget relabeled 5.1.0 as x64 (#50494)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** NA — caught from a failing `Update Fleet-maintained
apps` ingest run.

# What this does

Changes `installer_arch` from `x86` to `x64` for the Gpg4win Windows
FMA, and regenerates its output (5.0.2 → 5.1.0).

## Why the ingest was failing

```
{"level":"INFO","msg":"ingesting winget app","name":"Gpg4win"}
panic: ingesting winget app: failed to find installer for app
```

This is **not** a removed winget package — Gpg4win is still published as
`GnuPG.Gpg4win`. The manifest was fetched and parsed fine; the failure
is the `selectedInstaller == nil` check in
`ee/maintained-apps/ingesters/winget/ingester.go`, which means no
installer entry matched all four selector fields from our input.

Upstream flipped the architecture label between versions:

| | 5.0.2 (last ingested) | 5.1.0 (new) |
|---|---|---|
| `Architecture` | **x86** | **x64** |
| Manifest generator | `wingetcreate 1.10.3.0` | `YamlCreate.ps1
Dumplings Mod` |

Our input pinned `x86` to match 5.0.2, so nothing matched once 5.1.0
landed in `winget-pkgs` (2026-08-03 18:11 UTC,
microsoft/winget-pkgs#409397). The ingester only walks to an older
version directory on a genuine 404 of the installer manifest — never
because the newest version's installer failed to match — so it panics
instead of falling back.

## The new x64 label is the correct one

Verified against the real installer rather than trusting either
manifest:

- The NSIS stub's PE header is i386, which is very likely what
`wingetcreate` guessed `x86` from. Installer stubs are almost always
32-bit, so stub arch says nothing about the payload.
- The payload ships 64-bit binaries in `bin/` (PE machine `0x8664`)
alongside a 32-bit `bin_32/` compatibility set.

So 5.0.2's `x86` was the inaccurate manifest and the bot corrected it.
Regenerated `sha256` also matches a fresh download of
`gpg4win-5.1.0.exe` bit-for-bit (`9682f282…20a2e`).

## Blast radius

Worth flagging: this one field blocked **the entire FMA ingest**, not
just Gpg4win. `failed to find installer for app` isn't matched by
`isTransientGitHubError`, so it returns up to `panic(err)` in
`cmd/maintained-apps/main.go`, which aborts the process before
`processOutput` writes *any* app's manifest. Because the `ingesters` map
is iterated in random order, this could take out the homebrew side too.
Any future upstream flip of `installer_arch` / `installer_scope` /
`installer_type` / `installer_locale` on any single app will do the same
thing — worth a follow-up to make non-transient per-app ingest errors
skip-and-report instead of fatal.

## Why no changes file

The net user-visible effect is a routine FMA version bump (Gpg4win
5.1.0), which the scheduled ingest PR delivers without changelog
entries. Happy to add one if you'd rather it be called out.

# Checklist for submitter

- [x] 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

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

Verification performed:

- `go run ./cmd/maintained-apps -slug gpg4win/windows` completes with no
panic (previously fatal).
- Diff is scoped to the two expected files; `outputs/apps.json` is
untouched, since name/description didn't change.
- Regenerated `sha256` matches a fresh download of the upstream
installer.
- Install/uninstall scripts need no changes — both already enumerate the
native *and* `Wow6432Node` registry views across `HKLM`/`HKCU`, so
detection survives the 32→64-bit flip.
- `installer_arch` is only a manifest selector plus CI runner routing in
`.github/scripts/partition-fma-apps.sh`, where x64/x86/neutral all land
on the same x64 runner. No routing change, and the field is never
written into `outputs/`, so no user-visible arch claim changes.

Still needs the FMA validator's install/uninstall run on a Windows
runner to confirm 5.1.0 installs and is detected — that's what the draft
is for.
2026-08-04 09:50:53 -05:00
Allen Houchins b2b2081ad4 Remove Gadwin PrintScreen, PrintScreen Pro, and ScreenRecorder FMAs (expired TLS cert on download host) (#50470)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** NA

Removes the three Gadwin Fleet-maintained apps: **Gadwin PrintScreen**,
**Gadwin PrintScreen Pro**, and **Gadwin ScreenRecorder**.

All three download their installers from `www.gadwin.com`, whose Let's
Encrypt certificate **expired 2026-08-02** and has not been renewed:

```
subject=CN=gadwin.com
issuer=C=US, O=Let's Encrypt, CN=R13
notBefore=May  4 13:06:54 2026 GMT
notAfter=Aug  2 13:06:53 2026 GMT
```

Every FMA validation run now fails these three apps:

```
level=ERROR msg="Error downloading maintained app: downloading installer:
performing request for URL https://www.gadwin.com/download/PrintScreen650_Win64.msi:
tls: failed to verify certificate: x509: certificate has expired or is not yet valid"
app="Gadwin PrintScreen"
```

This is not just CI: Fleet fetches the installer from that same URL when
a user installs the app, so all three are currently uninstallable for
customers.

> [!NOTE]
> The certificate expired only one day before this PR was opened. If
Gadwin renews it, these apps become viable again and the alternative fix
is `"frozen": true` in each winget input (which skips validation) rather
than removal. Removing was chosen because a lapsed auto-renewing
certificate means no one is maintaining the download host, and a broken
installer URL is worse for users than an absent app. Happy to switch to
a freeze if reviewers prefer to wait it out.

### What's removed

Per app, all locations that applied:

| Location | PrintScreen | PrintScreen Pro | ScreenRecorder |
|---|---|---|---|
| `ee/maintained-apps/inputs/winget/<slug>.json` |  |  |  |
| `ee/maintained-apps/outputs/<slug>/windows.json` |  |  |  |
| `ee/maintained-apps/outputs/apps.json` entry |  |  |  |
| `frontend/.../icons/<Name>.tsx` |  |  | — (none existed) |
| import + mapping in `icons/index.ts` |  |  | — |
| `website/assets/images/app-icon-<slug>-60x60@2x.png` |  |  | — (none
existed) |

All three are Windows-only (no Homebrew input or `darwin.json`), so no
macOS counterpart is affected and no shared icons needed to be retained.
`apps.json` goes from 1393 to 1390 apps. The diff is deletion-only.

# Checklist for submitter

- [x] 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

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

Verification performed:

- `apps.json` parses as valid JSON; no `gadwin` slugs remain.
- Repo-wide grep for `gadwin` returns no dangling references. (The one
remaining hit, `cmd/osquery-perf/software-library/software.sql`, is a
simulated host-inventory fixture, not an FMA definition — intentionally
left in place.)
- `npx tsc --noEmit` reports no errors related to the removed icon
components or the icon index.
- `npx prettier --check` passes on `icons/index.ts` and `apps.json`.
- `go build ./cmd/maintained-apps/... ./ee/maintained-apps/...` and `go
test ./ee/maintained-apps/...` pass.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Removed Gadwin PrintScreen, Gadwin PrintScreen Pro, and Gadwin
ScreenRecorder from the maintained Windows application catalog.
* Removed their associated software listings, installation details, and
product icons from the application interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 21:37:09 -05:00
Allen Houchins 2431d580b0 Add QEMU as a Windows Fleet-maintained app (#50471)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #50126

Adds **QEMU** (`SoftwareFreedomConservancy.QEMU`) as a Windows
Fleet-maintained app, requested by a customer in #50126. Windows-only:
on macOS, Homebrew ships QEMU as a formula (CLI tools, no `.app`
bundle), so it isn't a viable macOS FMA.

## App identity (verified against the real installer)

- Downloaded `qemu-w64-setup-20260501.exe` from `qemu.weilnetz.de`;
SHA256 matches the winget manifest.
- QEMU's NSIS definition
([`qemu.nsi`](https://gitlab.com/qemu-project/qemu/-/blob/master/qemu.nsi))
writes the uninstall key `HKLM\...\Uninstall\QEMU` (64-bit view via
`SetRegView 64`) with `DisplayName "QEMU"` and `DisplayVersion` set to
the meson project version — which equals the winget `PackageVersion`, so
the patch policy reconciles cleanly.
- **No `Publisher` value is written to the registry**, so the default
generated exists query (`... AND publisher = 'QEMU Community'`) would
never match. The input overrides it with `exists_query: SELECT 1 FROM
programs WHERE name = 'QEMU';` — exact name match, so entries like "QEMU
guest agent" (virtio-win) are left alone.

## Install/uninstall scripts

- NSIS installer, machine scope, x64. Install: standard silent `/S`
(same pattern as AnyBurn).
- Uninstall: registry lookup by exact DisplayName across both registry
views, defensive UninstallString parsing, `/S _?=<installdir>` so the
uninstaller runs in place (instead of relaunching from `%TEMP%` and
returning immediately), verification that the ARP entry is actually
gone, then sweep of the leftover uninstaller/install dir,
`HKLM\SOFTWARE\QEMU`, and shortcuts.

## Version caveat

winget's newest version dir for this package is **11.0.50 — a QEMU
development snapshot** (QEMU uses `x.y.50` for post-release dev builds;
only x64, no arm64), added upstream alongside the 11.0.0 stable release.
The ingester picks the highest version, so this FMA currently ships the
snapshot build; it will move to the next stable (e.g. 11.0.1/11.1.0) as
soon as winget has it. The installer URL is date-pinned
(`.../2026/qemu-w64-setup-20260501.exe`), so there's no hash-drift risk.
If we'd rather not offer dev snapshots, that needs an ingester-level
version filter — flagging for maintainer input rather than building it
into this PR.

## Icon

Official 128×128 QEMU icon from the upstream source tree
(`ui/icons/qemu_128x128.png`), generated via
`tools/software/icons/generate-icons.sh`.

# Checklist for submitter

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

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] Verified installer SHA256, registry identity
(DisplayName/DisplayVersion/no Publisher), and silent switches against
the real installer and upstream `qemu.nsi`/`meson.build`
- [ ] `test-fma-windows-pr-only` validates QEMU install/uninstall in
this PR's CI


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added QEMU to the Windows software catalog.
* Added support for silent QEMU installation and reliable
uninstallation.
  * Added QEMU version detection and upgrade validation.
  * Added a QEMU icon to the software interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 21:36:12 -05:00
Allen Houchins 89acb97395 Add NVDA as a Windows Fleet-maintained app (#50450)
**Related issue:** Resolves #50125

Adds NVDA as a Windows Fleet-maintained app, from winget `NVAccess.NVDA`
(2026.1.1, NSIS/nullsoft, x86 launcher).

## Identity — read out of the shipped installer, not the manifest

I downloaded the 60 MB installer, extracted the NSIS payload, and read
the identity fields from `_buildVersion.pyc` and the PE headers. The
winget manifest is misleading in two ways:

| Field | winget says | Actually is | Source |
|---|---|---|---|
| Architecture | `x86` | **x64** app behind a 32-bit NSIS launcher stub
| `nvda_noUIAccess.exe` / `nvda_slave.exe` PE headers |
| Registry DisplayName | PackageName `NVDA` | **`NVDA 2026.1.1`** |
`source/installer.py` `getUninstallerRegInfo()`: `DisplayName=f"{name}
{version}"` |
| Publisher | `NV Access` | `NV Access` (matches) | `_buildVersion.pyc`:
`publisher = "NV Access"` |

Two consequences:

- Because NVDA itself is a **64-bit** process, it registers under the
native registry view, **not** `Wow6432Node` (the launcher's 32-bit-ness
is irrelevant). Both scripts check both views anyway, for legacy 32-bit
copies.
- DisplayName carries the version, so this needs `fuzzy_match_name:
true` → `name LIKE 'NVDA %'`. Publisher matches the locale manifest, so
no `program_publisher` override.

`installer_arch` stays `x86` because that's what the manifest declares
and the ingester matches on it.

## Version reconciles without a validator exception

DisplayVersion is the 4-part `2026.1.1.55980` (`version_detailed`)
against winget's `2026.1.1`:

- **Validator:** passes via the existing
`strings.HasPrefix(result.Version, appVersion+".")` branch in
`cmd/maintained-apps/validate/windows.go`. No new skip added —
deliberately, since existence-only skips make patch policies always
report "patched".
- **Patch policy:** `version_compare('2026.1.1.55980', '2026.1.1')` is
`> 0`, so an installed copy reads as newer, not outdated. No perpetual
false "update available".

## The install script can't trust the exit code

`source/gui/installerGui.py` `doInstall()` pops `winUser.MessageBox` /
`gui.messageBox` on **every** install failure path with **no `if silent`
guard**, and then falls through and exits **0**. Under SYSTEM in session
0 that means:

1. a failure **hangs forever** — nobody can click Retry/Cancel; and
2. if it were dismissed, a failed install would report **success**.

So `nvda_install.ps1` uses a watchdog plus an Add/Remove Programs
registration poll as the real success signal — the same shape as the
existing `azure_data_studio_install.ps1`. Timeouts are 420 + 120 + 30 =
570s, under the caller's 10-minute cap.

On timeout it kills only the launcher's `%TEMP%` children
(`nvda_noUIAccess` / `nvda_uiAccess`), **deliberately not `nvda.exe`** —
an installed NVDA runs as `nvda.exe`, and force-killing it would cut off
a signed-in user's screen reader with no warning.

## Uninstall

Vendor-documented `/S` (NVDA user guide, "Uninstalling NVDA"), plus
`_?=` last so the NSIS uninstaller runs in place instead of relaunching
from `%TEMP%` and returning immediately. NVDA writes **no**
`QuietUninstallString`, and its `UninstallString` is an **unquoted path
containing spaces** (`C:\Program Files\NVDA\uninstall.exe`), so the
parser handles that form. The directory comes from NVDA's `InstallDir`
value (not `InstallLocation`). Absence of the ARP entry is the success
signal, since NVDA removes it via `nvda_slave.exe unregisterInstall`.

## Reviewer notes

- **`installer_scope` is `""`, not `"machine"`.** NVDA genuinely
installs machine-wide (`%ProgramFiles%\NVDA` + HKLM), but the winget
manifest declares no `Scope`, so the ingester derives `""` and
`"machine"` panics with "failed to find installer". The one-line
ingester fix for this is designed in #48248 but isn't in `main`; I chose
not to change shared installer-selection code for a single-app addition.
Happy to land that fix here instead if preferred.
- **Upgrade caveat:** if NVDA is running for a signed-in user,
`--install-silent` refuses to overwrite its own running files by design
(`installer.py` `install()`). The script fails with an actionable
message rather than force-killing the screen reader.
- Installer URL is version-pinned
(`download.nvaccess.org/releases/2026.1.1/...`), not a "latest"
redirect. SHA verified against my own download of the file.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

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

`go test ./ee/maintained-apps/...` passes; prettier and `tsc --noEmit`
are clean. I have no Windows host or `pwsh`, so **the install/uninstall
scripts are unexercised** until FMA validation CI runs them on a Windows
runner. No changes file — consistent with other FMA additions (#50415,
#50348, #50352).
2026-08-03 12:40:44 -05:00
Allen Houchins 51044325e7 Treat MSI reboot-required exit codes as success and quote the msiexec log path (#50407)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** NA

## What & why

Two independent defects in the default MSI scripts. Neither has any
server-side handling, so the script text is the only place either can be
fixed.

### 1. Reboot-required exit codes reported as install failures

The default MSI install script (`pkg/file/scripts/install_msi.ps1`)
ended with `Exit $installProcess.ExitCode`, passing msiexec's raw exit
code straight through. An install that **succeeded but requested a
reboot** therefore reported as a failed install:

- `3010` — `ERROR_SUCCESS_REBOOT_REQUIRED`
- `1641` — `ERROR_SUCCESS_REBOOT_INITIATED`

Both default MSI *uninstall* scripts (`uninstall_msi.ps1` and
`uninstall_msi_with_upgrade_code.ps1`) already carve these out via
`$successCodes = @(0, 3010, 1641)` — install was the only MSI script
missing it. This change adds the same check, using the identical idiom
and comment wording as the uninstall scripts.

### 2. Unquoted log file path in the `/lv` argument

The default MSI install and remove scripts passed the log path unquoted:

```powershell
-ArgumentList "/quiet /norestart /lv ${logFile} /i `"${env:INSTALLER_PATH}`""
```

`Start-Process` appends a single-string `-ArgumentList` to the command
line verbatim — it adds no quoting of its own. `${env:INSTALLER_PATH}`
was already protected by escaped quotes; `${logFile}` was not. So when
`TEMP` contains a space, msiexec tokenizes the path on whitespace: `/lv`
receives only the chunk up to the first space (`C:\Users\John`), and the
remainder (`Smith\AppData\...\fleet-install-software.log`) is left as a
stray token, which msiexec rejects as an invalid command line (`1639`).
The install fails outright rather than merely writing its log somewhere
unexpected.

The fix quotes it the way `${env:INSTALLER_PATH}` already was:

```powershell
-ArgumentList "/quiet /norestart /lv `"${logFile}`" /i `"${env:INSTALLER_PATH}`""
```

**On severity:** this is latent under normal fleetd operation. Install
scripts inherit `os.Environ()` from orbit
(`orbit/pkg/installer/installer.go`), which runs as a LocalSystem
service, so `TEMP` is `C:\Windows\TEMP` — no spaces. It bites when the
system `TEMP` is redirected to a path containing a space, or when an
admin copies the script (Fleet renders it in the UI) and runs it in a
user context whose profile name contains a space. Not reproduced on a
Windows host; the analysis is from msiexec's whitespace tokenizing, not
from an observed failure.

The newer hand-written FMA scripts (`mozilla-vpn_install.ps1`,
`egnyte_install.ps1`, `vnc-server_install.ps1`,
`vnc-viewer_install.ps1`, `agent-ransack_install.ps1`) already used the
quoted form. This brings the older ones in line with them.

## Scope

`GetInstallScript("msi")` feeds two paths, both fixed by change 1:

1. The default install script for **user-uploaded MSI packages**
(`ee/server/service/software_installers.go`).
2. The generated install script for **MSI-based Fleet-maintained apps**
(`ee/maintained-apps/ingesters/winget/ingester.go`).

Change 2 additionally covers `remove_msi.ps1` (the uninstall script used
for packages added before the uninstall feature shipped) and the nine
hand-written winget install scripts that still carried the unquoted
form: `azure-functions-core-tools`, `bluej`, `crisisgo`,
`delinea-connection-manager`, `geogebra-classic`, `google-ads-editor`,
`gotomeeting`, `imageglass`, `sourcetree`.

Notes:

- **FMA outputs are not regenerated here.** `install_script_ref` is
content-addressed, and existing `outputs/*/windows.json` files carry
both the ref and the script text, so they stay internally consistent.
The ingest workflow runs every 4 hours and will roll the refs for
MSI-based apps forward on its own. Regenerating them in this PR would
produce a huge diff and trigger Windows FMA validation for every MSI
app.
- Several per-app FMA install scripts exist **only** to add the
exit-code carve-out and become redundant once this lands (for example
`scribe_install.ps1` from #50341). They are harmless duplicates of the
new default and can be removed in follow-up. Per-app scripts that do
other work too (e.g. `delinea-connection-manager_install.ps1` forcing
`ALLUSERS=1`) still need to keep their own copy — those got the quoting
fix instead.
- Neither change applies to `uninstall_msi.ps1` or
`uninstall_msi_with_upgrade_code.ps1`: they already handle the reboot
codes, and they build `-ArgumentList` as an array with no `/lv` argument
at all.
- `install_exe.ps1` deliberately left alone — EXE installers have no
standard exit-code convention, which is why they use per-app scripts.
- The per-app example scripts embedded in `articles/` (CrowdStrike,
Cloudflare WARP, SentinelOne) are separate copy-paste content and are
not touched.

# Checklist for submitter

- [ ] 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.

No changes file is currently in this PR — the earlier one was removed.
Both fixes change user-visible install/uninstall outcomes, so one may be
warranted before merge.

- [x] 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.

Change 2 is precisely this: a path interpolated into a command line is
now quoted so whitespace can't split it into extra arguments.

## Testing

- [x] Added/updated automated tests

`pkg/file`'s golden test (`TestGetInstallAndRemoveScript`) covers the
script contents; each script and its golden were changed in lockstep, so
they remain byte-identical. `go test ./pkg/file/ -run Script` and `go
test ./ee/server/service/ -run TestGetInstallScript` pass. Goldens can
be regenerated with `go test ./pkg/file/... -update`.

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

Not QA'd on a Windows host. Change 1 needs an MSI that returns 3010
under Fleet's SYSTEM context to confirm the install now reports success.
Change 2 needs an MSI install run with `TEMP` pointed at a path
containing a space.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved MSI installation and removal reliability when log-file paths
contain spaces.
* MSI installations requiring a restart are now recognized as
successful.
* Standard MSI success and restart-required results are handled
consistently while other errors remain available for troubleshooting.
* Updated supported application installers to use the more reliable
logging behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 09:52:31 -05:00
kitzyandAllen Houchins 4036f9bfcb Add Microsoft ODBC Driver 18 for SQL Server as a Windows Fleet-maintained app (#50348)
**Related issue:** Resolves #50327

Adds Microsoft ODBC Driver 18 for SQL Server as a Windows
Fleet-maintained app, from winget `Microsoft.msodbcsql.18` (18.6.2.1,
MSI, machine scope, x64, en-US). Sibling of #50342 (driver 17); the two
install side by side and are separate products.

## Verification

Identity read from the MSI Property table:

```
ProductName    Microsoft ODBC Driver 18 for SQL Server
Manufacturer   Microsoft Corporation
ProductCode    {820A3DEC-9783-42AE-B12D-750FCCF07E10}
UpgradeCode    {ADA68B65-BFF8-4E6A-B082-CC6682D425B8}
ALLUSERS       1
```

- Installer SHA confirmed against a local download (`20314529…4b82`).
- The UpgradeCode differs from driver 17's (`{0123A210-…}`), which
confirms they are independent products rather than upgrades of one
another. The generated uninstall script picked up 18's, so uninstalling
one will not touch the other.
- Because `ProductName` already carries the major version, the exists
query is a simple equality — no `version LIKE '18.%'` pinning needed,
and no risk of 17 and 18 matching each other.

Like driver 17, the MSI refuses to install without
`IACCEPTMSODBCSQLLICENSETERMS=YES`, which Fleet's default MSI script
does not pass, so this ships a small custom install script. It is
deliberately a **separate file** from driver 17's rather than a shared
path, following the repo's one-script-per-app convention and keeping the
two PRs independently mergeable.

The icon comes from the MSI's own `ARPPRODUCTICON` stream. As with
#50342 it is natively 32×32, so the 128×128 asset is an upscale — happy
to drop it in favour of the generic fallback if reviewers prefer. The
icon map key also needed the same manual correction (the generator
derives it from the slug, which omits "for sql server").

The manifest declares a `Microsoft.VCRedist.2015+.x64` dependency, which
the ingester ignores; we ship `vc-redist-x64/windows` and the
redistributable is present on most hosts.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Microsoft ODBC Driver 18 for SQL Server to the maintained
Windows software catalog.
* Added support for version 18.6.2.1, including installation, upgrade,
uninstall, detection, and license acceptance handling.
  * Added the software’s icon and catalog display details.
* Included support for SQL Server and Azure SQL connectivity scenarios.
* Installation now provides appropriate handling for successful
completion, restart requirements, and installation failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-03 09:44:54 -05:00
Allen Houchinsandcoderabbitai[bot] a4d058384c Add CrystalDiskMark as a Windows FMA (#50415)
**Related issue:** Resolves #50322

Adds CrystalDiskMark as a Windows Fleet-maintained app, from winget
`CrystalDewWorld.CrystalDiskMark` (9.0.3, Inno Setup, machine scope,
x64). Found in a customer's ManageEngine ServiceDesk Plus Windows
deployment catalog with no Fleet equivalent.

## Identity — read out of the Inno header, not the manifest

The winget manifest carries no `AppsAndFeaturesEntries`, so the ARP
identity had to come from the installer itself. I LZMA-decompressed the
Inno setup-data block and read the header directly:

| Header field | Value |
|---|---|
| `AppName` | `CrystalDiskMark` |
| `AppVerName` | `CrystalDiskMark 9.0.3` |
| `AppVersion` | `9.0.3` |
| `AppPublisher` | `Crystal Dew World` |
| `AppId` | `CrystalDiskMark9` |
| `UninstallDisplayName` | *(empty)* |
| `DefaultDirName` | `{pf}\CrystalDiskMark9` |

With `UninstallDisplayName` empty, Inno falls back to `AppVerName` for
the ARP `DisplayName` — so this registers as **`CrystalDiskMark
9.0.3`**, version-suffixed, and the exists query is a prefix match
(`fuzzy_match_name: true`).

That the name is version-suffixed isn't inferred from "Inno usually does
this." The Aoi edition's header sets `AppVerName` to `CrystalDiskMark
9.0.3 Aoi Edition` while its `AppName` is `CrystalDiskMark Aoi Edition`
— the compiler's synthesized default would have been `CrystalDiskMark
Aoi Edition 9.0.3`, so the script is setting `AppVerName` deliberately,
and Setup will write exactly that string.

`AppPublisher` matches the winget locale `Publisher` verbatim, so no
`program_publisher` override is needed, and `DisplayVersion` is
`AppVersion` = `9.0.3`, which reconciles with the FMA version with no
validator exception.

Generated query:

```sql
SELECT 1 FROM programs WHERE name LIKE 'CrystalDiskMark %' AND publisher = 'Crystal Dew World';
```

## Silent flags — the `[Run]` entries were decoded, not assumed

The header contains three `[Run]` entries with
`{cm:LaunchProgram,CrystalDiskMark9}` descriptions (one per
architecture), which is exactly the shape that has hung silent Inno
installs for us before. I parsed the entry structure through to the flag
bytes:

- wait enum = `1` → `nowait`
- flags = `0x14` → bit 2 `postinstall` + bit 4 `skipifsilent`

So `/VERYSILENT /SUPPRESSMSGBOXES /NORESTART` will not launch the GUI,
and there is no post-install hang to work around. The install script
still uses the poll-and-kill wrapper rather than `-Wait`, plus an
ARP-registration wait, since the installer can return before the
registry entry lands.

`/ALLUSERS` is deliberately omitted: `DefaultDirName` is `{pf}`, which
requires admin install mode already, so the switch would be a no-op.

## Uninstall

`UninstallString` is Inno's quoted `unins000.exe` path, but the script
uses the three-branch defensive parser (quoted / unquoted-with-spaces /
bare token) anyway. Two app-specific bits:

- The 12 `DiskMark*.exe` binaries are stopped first — the uninstaller
won't proceed while the `CrystalDiskMark9` mutex is held.
- The script waits for the registry entry to disappear rather than
trusting the exit code. Inno's uninstaller relaunches itself from a temp
copy and the original process returns early, so its exit code is not a
completion signal.

## Icon

The setup exe's `MAINICON` is Inno's stock CD-and-box artwork, not the
app's, so the icon was extracted from `DiskMark64.exe`'s own resources
(`innoextract` → `wrestool -t 14 -n 130` → `icotool`, 256×256 32-bit).

## Two things for reviewers

1. **This carries a shared-code change to the winget ingester**
(`normalizeSourceForgeURL`). A bare SourceForge project file URL serves
non-browser clients a 133KB HTML landing page with a 200, so the first
validator run downloaded that instead of the installer and failed on the
hash. Only the `.../download` form serves the binary. WinSCP works today
only because its manifest happens to carry the suffix, so the fix
normalizes in the ingester rather than depending on manifest authors.
WinSCP regenerates byte-identical and is the only other
SourceForge-hosted app in the catalog; `TestNormalizeSourceForgeURL`
covers both shapes. Details in [this
comment](https://github.com/fleetdm/fleet/pull/50415#issuecomment-5162501140).
2. **The Aoi and Shizuku editions share this package's `AppId` and
install directory.** They occupy the same ARP slot
(`CrystalDiskMark9_is1`), can't be co-installed, and will match the
exists query as `CrystalDiskMark 9.0.3 <Edition>`. I treated that as
correct rather than something to exclude — it is the same product at the
same version — but flagging it in case we'd rather pin to the plain
edition.

## Verification

- Inno header dumped offline; `AppVerName` / `AppPublisher` /
`AppVersion` read directly, `[Run]` flag bytes decoded.
- Generated SHA matches the winget installer manifest
(`1a255154…917e5e`), and the download was verified through the exact
code path the validator uses (`maintained_apps.DownloadInstaller` with
`http.DefaultClient`) — 4,523,144 bytes, hash matches.
- `apps.json` valid, description filled, icon generated and inserted
alphabetically in `index.ts` under the key `crystaldiskmark`.
- `go test ./ee/maintained-apps/...` passes; `GOOS=windows go build
./cmd/maintained-apps/validate/` builds; `gofmt`/`go vet` clean. `make
lint-go-incremental` could not run in my environment (`custom-gcl` fails
to clone golangci-lint, pre-existing and unrelated).
- First validator run failed on the download issue above and is fixed;
the full validator pass (install → detect → uninstall on a Windows host)
still needs to go green before this leaves draft.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added CrystalDiskMark as a supported Windows application, including
installation, uninstallation, detection, categorization, and software
catalog details.
  * Added CrystalDiskMark branding to the software interface.
* Improved SourceForge installer URL handling by automatically appending
download paths when needed.

* **Bug Fixes**
* Preserved existing download URLs, query parameters, and unsupported or
malformed URLs during normalization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-08-03 09:03:24 -05:00
Luís Teles 1dc1a51313 Fix Windows Git FMA patch policy never detecting outdated installs (#50424)
**Related issue:** Resolves #50283

Git for Windows registers itself in the Windows uninstall registry as
exactly `Git` — its Inno Setup script has set
`UninstallDisplayName={#APP_NAME}` since
[build-extra#365](https://github.com/git-for-windows/build-extra/pull/365)
(2021). The generated queries matched `programs.name LIKE 'Git %'`,
which cannot match that name, so the patch policy's `NOT EXISTS (...)`
was always true and every host reported `Pass` regardless of installed
version — update automations never fired. The same mismatch meant an
existing Git install couldn't be matched to the maintained app.

The input now uses the custom fuzzy pattern `Git%`, which also covers
the pre-2021 `Git <version>` DisplayName form (the oldest installs,
which are exactly what a patch policy needs to flag), and relies on the
existing `publisher = 'The Git Development Community'` guard to exclude
GitHub Desktop, Git LFS, GitKraken and Git Extensions. This is the same
match the app's own uninstall script and the FMA Windows CI workflow
already use.

Instances that already created this policy pick up the corrected query
when the next Git version becomes active, since the patch policy query
is regenerated from the active installer.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`.

## Testing

Verified the `LIKE` semantics in SQLite against real-world `programs`
rows (old pattern misses `Git`, new pattern matches both DisplayName
forms, publisher guard still excludes GitHub Desktop / Git LFS /
GitKraken / Git Extensions), and confirmed winget's `PackageVersion`
matches the registry `DisplayVersion` for Git so up-to-date hosts still
pass. `outputs/git/windows.json` was regenerated with the ingester
rather than hand-edited. Not manually QA'd on a Windows host — relying
on `test-fma-windows` validation.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved Git for Windows detection across supported environments.
* Outdated installations can now be correctly identified and included in
update automation, including registrations named “Git.”
  * Existing publisher and version checks remain unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 07:53:08 -05:00
kitzy 86f4619faf Add Microsoft ODBC Driver 17 for SQL Server as a Windows FMA (#50342)
**Related issue:** Resolves #50326

Adds Microsoft ODBC Driver 17 for SQL Server as a Windows
Fleet-maintained app, from winget `Microsoft.msodbcsql.17` (17.11.1.1,
MSI, machine scope, x64, en-US). Found in a customer's ManageEngine
ServiceDesk Plus Windows deployment catalog with no Fleet equivalent.

## The install would fail without a custom script

The MSI refuses to install unless `IACCEPTMSODBCSQLLICENSETERMS=YES` is
passed. This is not just a winget convention — the MSI carries the
condition and the matching error string:

```
IACCEPTMSODBCSQLLICENSETERMS ~=
IACCEPTMSODBCSQLLICENSETERMS=YES command-line parameter is missing.
```

Fleet's default MSI install script passes only `/quiet /norestart /i`,
so without the small custom script in this PR every install would fail.
Uninstall is still the auto-generated upgrade-code script, which is
correct as-is.

## Verification

Identity read from the MSI Property table:

```
ProductName    Microsoft ODBC Driver 17 for SQL Server
Manufacturer   Microsoft Corporation
ProductCode    {ACBA503E-64A9-4A8A-A6CC-63656F395618}
UpgradeCode    {0123A210-9B73-46E7-B5CE-7F33630300E7}
ALLUSERS       1
```

- Installer SHA confirmed against a local download (`0f642870…bd4e`).
- `ALLUSERS=1` means it always installs per-machine.
- The generated uninstall script correctly picked up the **x64**
UpgradeCode — note the x86 build uses a different one, so an arch mix-up
here would silently fail to uninstall.

**No version pinning is needed in the exists query.** When I scoped this
I expected to need `AND version LIKE '17.%'` the way the Amazon Corretto
FMAs do, but the `ProductName` already carries the major version, so
`Microsoft ODBC Driver 17 for SQL Server` and `...18...` are naturally
distinct entries. Simple equality is correct and unambiguous.

## Two notes for reviewers

**Icon quality.** The MSI's `ARPPRODUCTICON` stream tops out at 32×32,
so the 128×128 asset here is an upscale and looks soft. It is the
authentic Add/Remove Programs icon, but if we would rather fall back to
the generic software icon than ship a blurry one, drop the icon files
and I will regenerate without them.

**Icon map key needed a manual fix.** The generator derives its key from
the slug and produced `"microsoft odbc driver 17"`, but lookups use the
lowercased catalog name — `"microsoft odbc driver 17 for sql server"`.
Corrected by hand. Same generator gap hit Paint.NET in #50340.

**Dependency.** The manifest declares `Microsoft.VCRedist.2015+.x64`.
The ingester ignores winget `Dependencies`, but unlike HandBrake
(#50323) we do ship a matching FMA (`vc-redist-x64/windows`), and the
redistributable is present on most Windows hosts already. Noting it
rather than treating it as a blocker.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added Microsoft ODBC Driver 17 for SQL Server to the maintained
software catalog.
* Added Windows installation and upgrade support for version 17.11.1.1.
  * Added the software’s icon and catalog display details.
* Included silent installation, logging, license acceptance, and reboot
handling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 00:30:07 -05:00
kitzyandAllen Houchins 332d8a2cee Add Microsoft Access Database Engine 2016 Redistributable as a Windows Fleet-maintained app (#50365)
**Related issue:** Resolves #50325

Adds Microsoft Access Database Engine 2016 Redistributable as a Windows
Fleet-maintained app, from winget `Microsoft.AccessDatabaseEngine2016`
(16.0.5044.1000, x64). Found in a customer's Windows deployment catalog
with no Fleet equivalent.

## No ingester change was needed after all

When I scoped this I expected to need a one-line change, because winget
types the installer as `portable`, which is not in the ingester's
`vendorTypes` or `fileTypes`. I tested it first: `"installer_type":
"portable"` with an empty scope matches cleanly, and since the generated
output carries no installer type (Fleet infers `.exe` from the URL), the
result is correct as-is.

This is the first FMA input to use `portable`. If reviewers would rather
not establish that precedent, adding `portable` alongside
`wix`/`nullsoft`/`inno` in `vendorTypes` would let the type fall through
to the URL extension and the input could then say `exe`. Both work. I
chose the one that touches no shared code.

## Identity: winget's name is wrong twice over

winget's locale manifest says `Microsoft Access Database Engine 2016
Redistributable`. The MSI says:

```
ProductName    Microsoft Access database engine 2016 (English)
Manufacturer   Microsoft Corporation
ProductCode    {90160000-00D1-0409-1000-0000000FF1CE}
UpgradeCode    {00160000-00D1-0000-1000-0000000FF1CE}
ALLUSERS       1
```

Two traps: the real name is lowercase `database engine`, and it carries
an `(English)` locale suffix with the LCID (`0409`) embedded in the
ProductCode. osquery's `=` on TEXT is case-sensitive, so the
winget-derived name would have matched nothing.

## x64 only, deliberately

The customer deploys both architectures, but **x86 and x64 register the
identical DisplayName**. I confirmed this by extracting both installers:

| Arch | ProductName | UpgradeCode |
|---|---|---|
| x64 | `Microsoft Access database engine 2016 (English)` |
`{00160000-00D1-0000-1000-0000000FF1CE}` |
| x86 | `Microsoft Access database engine 2016 (English)` |
`{00160000-00D1-0000-0000-0000000FF1CE}` |

They differ only by product and upgrade code. Two FMAs could not be told
apart by an exists query, so installing one would make the other report
itself installed. Shipping x64 only avoids that. This also corrects my
earlier note on the issue suggesting the architecture go in the catalog
name; that would distinguish the catalog entries but not the detection
queries, so it would not have helped.

The uninstall script resolves the product from the x64 upgrade code
rather than a product code, so it removes the x64 build and leaves an
x86 install alone.

## The Office conflict is handled, not hidden

This redistributable refuses to install when Office of the opposite
bitness is present. Following the same approach @allenhouchins set out
for HandBrake in #50323, the install script checks the Click-to-Run
platform first and exits with an actionable message instead of a bare
installer failure:

```
32-bit Microsoft Office is installed on this host (Click-to-Run platform: x86).
The 64-bit Access Database Engine cannot be installed alongside it. Use the 32-bit redistributable instead.
```

**A green validator here proves less than usual.** The CI runner has no
Office installed, so it exercises the happy path only. The conflict this
app is known for cannot be reproduced there. That is a limitation of the
environment, not evidence the app is safe on managed hosts, and it is
the main thing worth weighing before merging.

## Other notes

- Installer SHA confirmed against a local download of
`accessdatabaseengine_X64.exe` (`04e96c9f…03de`).
- The download is a self-extracting package. Its `setup.cmd` runs
`InstallUCRT.exe` and then `msiexec %1 /i AceRedist.msi`, which is why
`/quiet` reaches the MSI.
- **No icon.** The MSI has no `ARPPRODUCTICON` and no Icon table, so
there is nothing to extract. The app falls back to the generic software
icon, which suits a redistributable with no user-facing app.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Microsoft Access Database Engine 2016 Redistributable as a
supported Windows application.
* Added silent installation and uninstallation workflows with
architecture checks, registration detection, timeout handling, and
standard success-code support.
* Included package metadata, version information, download verification,
Developer tools categorization, and reliable installation-state
detection.
* Added handling for existing installations and validated installer
outcomes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-02 22:59:48 -05:00
kitzy 5fa8a0a9ea Add Microsoft .NET Desktop Runtime 10 as a Windows FMA (#50361)
**Related issue:** Resolves #50360

Adds Microsoft .NET Desktop Runtime 10 as a Windows Fleet-maintained
app, from winget `Microsoft.DotNet.DesktopRuntime.10` (10.0.10, WiX burn
bundle, x64).

This closes the gap opened by HandBrake (#50323 / #50352). HandBrake
requires the .NET **Desktop** Runtime and its install script now
hard-fails without it, but Fleet had no FMA that could satisfy that — we
ship `microsoft-dotnet-runtime-8`/`-10`, which are the *base* runtime, a
different package with its own Add/Remove Programs entry. The customer's
ManageEngine catalog also deploys the Desktop Runtime directly.

## Verification

The winget manifest supplies `AppsAndFeaturesEntries`, and I confirmed
each value against the real installer by extracting the burn bundle's
registration data:

```
Microsoft Windows Desktop Runtime 10.0.10 (x64)
Publisher="Microsoft Corporation"
Version="10.0.10.50000"
```

- Installer SHA confirmed against a local download (`e82fc901…84d1`).
- The `DisplayName` carries both version and architecture, so the exists
query uses the same `LIKE 'Microsoft Windows Desktop Runtime 10.%' AND
name LIKE '%(x64)'` shape as the existing base-runtime FMAs.
- The bundle exposes several ProductCodes (the bundle plus its MSI
components), which is exactly the shape the existing uninstall script
already documents and handles.

**`use_display_version_for_patch` is required here.** The registry
`DisplayVersion` is `10.0.10.50000` but the winget package version is
`10.0.10`. Without the flag the patch policy would compare against the
marketing version and mis-order against what osquery reports. The
generated patched query correctly compares against `10.0.10.50000`.

## Reuse rather than duplication

- **Scripts:** this reuses `microsoft_dotnet_runtime_install.ps1` /
`_uninstall.ps1` unchanged. The Desktop Runtime is the same burn bundle
shape, and the uninstaller already resolves the bundle from the injected
`$PACKAGE_ID` with a Package Cache fallback. Those scripts are already
shared by the two base-runtime FMAs, so this follows the existing
pattern rather than adding a near-identical copy.
- **Icon:** reuses the existing `MicrosoftDotnetRuntime` component and
its `.NET` artwork. The burn bundle only carries a 32×32 icon, so
extracting one would have meant shipping a blurry upscale of the same
logo.

The new map key is `"microsoft .net desktop runtime"` (no version). Icon
lookup is a loose *prefix* match — `s === key || s.startsWith(key + "
")` — so one key covers 10 and any future major, mirroring how
`"microsoft .net runtime"` already serves both base-runtime FMAs. It is
longer than that key, and lookup sorts longest-first, so the desktop
runtime cannot be mis-matched to the base runtime icon.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added Microsoft .NET Desktop Runtime 10 to the Windows software
catalog.
* Added support for installing and uninstalling the x64 desktop runtime,
including version detection and reboot handling.
* Added a dedicated Microsoft .NET Runtime icon for the software
listing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-02 22:10:40 -05:00
kitzy b35904ccfa Add HandBrake as a Windows FMA (#50352)
**Related issue:** Resolves #50323

Adds HandBrake as a Windows Fleet-maintained app, from winget
`HandBrake.HandBrake` (1.11.2, NSIS, machine scope, x64). Found in a
customer's ManageEngine ServiceDesk Plus Windows deployment catalog with
no Fleet equivalent.

Per @allenhouchins' guidance on the issue, the `.NET Desktop Runtime 10`
prerequisite is handled in the install script with a clear failure
message rather than left to fail silently after install.

## The prerequisite check

HandBrake's own installer text states it plainly:

> HandBrake requires Microsoft .NET *Desktop* Runtime 10. If this is not
installed, you will be prompted to install it when you first run the
app.

The installer neither bundles nor installs that runtime, and the FMA
ingester drops winget's `Dependencies` field. Without a check, Fleet
would install HandBrake, register it in Add/Remove Programs, report
success, and the user would hit a runtime prompt on first launch. The
script now checks
`%ProgramFiles%\dotnet\shared\Microsoft.WindowsDesktop.App\10.*` and
exits 1 with an actionable message when it is absent.

Two things worth reviewers' judgement:

- **We fail rather than warn.** The vendor does prompt the user at first
run, so an install without the runtime is recoverable by hand. I chose
to fail because in a SYSTEM-context managed deployment an end-user
runtime prompt is a support ticket, and a truthful "install failed, here
is why" is more useful than a silently unusable app. Easy to soften to a
warning + `Exit 0` if we would rather defer to the vendor's prompt.
- **We ship no `.NET Desktop Runtime` FMA today.** Fleet has
`microsoft-dotnet-runtime-8`/`-10`, but those are the *base* runtime,
not the Desktop runtime — a separate package with its own ARP entry. So
there is currently no in-product way to satisfy this prerequisite; an
admin has to deploy the runtime by other means. That gap is worth its
own issue.

## Identity — two traps, both from reading the vendor's NSIS script

HandBrake's
[`Installer64.nsi`](https://github.com/HandBrake/HandBrake/blob/master/win/CS/HandBrake.Nsis.Installer/Installer64.nsi)
writes only four values:

```nsis
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
...
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayName"     "$(^Name)"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayIcon"     "$INSTDIR\HandBrake.exe"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayVersion"  "${PRODUCT_VERSION}"
```

1. **`DisplayName` is version-suffixed** (`HandBrake 1.11.2`), so the
exists query is a prefix match, not equality.
2. **No `Publisher` is ever written.** The default generated query would
have pinned `publisher = 'The HandBrake Team'` from the winget locale
manifest and matched nothing, forever, while the validator still passed.
The exists query is overridden to drop the publisher clause.

`UninstallString` is also an unquoted path containing spaces
(`C:\Program Files\HandBrake\uninst.exe`), which the defensive parser in
the uninstall script handles by capturing through `.exe`.

The uninstall script additionally waits for the registry entry to
disappear: a silent NSIS uninstaller returns before removal completes,
so its exit code alone is not a reliable completion signal.

## Verification

- Installer SHA confirmed against a local download (`6becb8e5…f8cd`);
the URL is GitHub Releases, so none of the SourceForge trouble from
#50322 applies.
- **No icon work needed.** Reusing the catalog name `HandBrake` means
this shares the existing `handbrake` icon with `handbrake-app/darwin`,
and the two group together in the FMA library.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added HandBrake 1.11.2 to the Windows maintained applications catalog.
* Added support for silent installation and uninstallation, including
version detection and installer verification.
* Added validation for the required .NET Desktop Runtime 10
prerequisite.
* Added handling for installation completion, reboot-required results,
and uninstall status reporting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-02 22:00:08 -05:00
kitzyandAllen Houchins 194df72c90 Add Scribe as a Windows Fleet-maintained app (#50341)
**Related issue:** Resolves #50331

Adds Scribe as a Windows Fleet-maintained app, from winget
`ColonyLabs.ScribeDesktopCapture` (6.7.23.0, MSI, machine scope, x64).
Found in a customer's ManageEngine ServiceDesk Plus Windows deployment
catalog with no Fleet equivalent.

The simplest of this batch: a plain machine-scope MSI, so the install
and upgrade-code uninstall scripts are auto-generated and no custom
scripts are needed.

## Verification

Identity read directly from the MSI Property table rather than inferred
from winget:

```
ProductName    Scribe
Manufacturer   Colony Labs, Inc
ProductCode    {87a51b1f-554d-414d-92a4-002a0916c91c}
UpgradeCode    {351EF756-3AF5-4117-8697-53AB61427040}
ALLUSERS       2
```

`Manufacturer` matches the winget locale `Publisher` exactly, so no
`program_publisher` override is needed. `ALLUSERS=2` confirms
per-machine install when elevated, which is how Fleet runs it. No
`ARPSYSTEMCOMPONENT`, so this is a real product entry and not a
bootstrapper.

- Installer SHA confirmed against a local download of
`Scribe_6.7.23.msi` (`41004c21…9b74`).
- Icon extracted from the MSI's own `Scribe for Windows.exe` resource,
not sourced from the web.

## Note on the name

`Scribe` is a generic `DisplayName`, so the exists query pins `publisher
= 'Colony Labs, Inc'` to avoid matching an unrelated product of the same
name.

This is **not** related to `timescribe/darwin`, which is already in the
catalog — different vendor, different product. It deliberately does not
share that catalog name or icon.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added Scribe to the maintained applications catalog for Windows.
* Added support for installing and uninstalling Scribe MSI packages,
including version detection and installer metadata.
* Added reliable installation handling with logging, elevated execution,
restart control, and support for standard successful installer exit
codes.
  * Added the Scribe app icon to the software interface.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-02 21:08:51 -05:00
kitzyandAllen Houchins da1947cd1f Add Paint.NET as a Windows Fleet-maintained app (#50340)
**Related issue:** Resolves #50330

Adds Paint.NET as a Windows Fleet-maintained app, from winget
`dotPDN.PaintDotNet` (5.1.12, machine scope, x64). Found in a customer's
ManageEngine ServiceDesk Plus Windows deployment catalog with no Fleet
equivalent.

## Identity: winget's metadata is wrong here

The winget locale manifest gives `PackageName: paint.net` (lowercase).
The actual registry `DisplayName` is **`Paint.NET`**, read straight out
of the MSI Property table:

```
ProductName    Paint.NET
Manufacturer   dotPDN LLC
UpgradeCode    {04A40F40-A207-4B48-AED7-6AA532E43275}
ALLUSERS       2
```

There is no `ARPDISPLAYNAME` override and no `ARPSYSTEMCOMPONENT`, so
`ProductName` is what lands in Add/Remove Programs. Taking the winget
name at face value would have produced an exists query that silently
never matches. `ALLUSERS=2` confirms it installs per-machine when run
elevated, which is how Fleet runs it.

## This is a zip-wrapped installer

Paint.NET publishes **only** `.zip` assets — there is no bare `.exe` or
`.msi` on the vendor's GitHub releases. So this uses `installer_type:
zip` with custom scripts, following the existing precedent of
`agent-ransack`, `adobe-acrobat-pro`, `vnc-server`, and `vnc-viewer`.
The install script extracts the archive and runs the nested installer
with `/auto`, the vendor's silent switch per the manifest's
`InstallerSwitches`.

**Uninstall resolves the product from the UpgradeCode, not the
ProductCode.** Paint.NET's ProductCode changes with every release, and
the `.exe` and `.msi` variants register *different* ProductCodes. The
UpgradeCode is stable — I verified it is identical across 5.1.10 and
5.1.12 — so `RelatedProducts` on it removes whichever variant is
present.

## One thing reviewers may want to change

The manifest offers six installers; three are x64/machine/zip and differ
only by `NestedInstallerType` (`exe`, `wix`, `portable`). The ingester's
selection loop takes the **first** match and breaks, so it picks the
`.install.x64.exe` bootstrapper. The `.winmsi.x64.zip` variant is
arguably the better FMA target — a plain MSI with predictable ARP
behaviour — but there is no way to express "prefer this nested type" in
the input today. Selecting it would need an ingester change, so I did
not do it here. Worth a follow-up if we hit trouble with the
bootstrapper.

## Verification

- Zip SHA confirmed against a local download (`3cd861b5…c867`); archive
contains exactly one file, `paint.net.5.1.12.install.x64.exe`.
- Icon extracted from that installer's own 256px resource, not sourced
from the web.
- Icon map key is `"paint.net"`, the lowercased catalog name. The icon
generator derives its key from the slug and produced `"paint dot net"`,
which would never have matched at runtime — corrected by hand.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added Paint.NET to the Windows software catalog.
* Added support for installing, upgrading, detecting, and uninstalling
Paint.NET.
  * Added Paint.NET branding and an icon to the software interface.
* Included Paint.NET version 5.1.12 with verified download metadata and
Productivity categorization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-08-02 20:40:48 -05:00
kitzy f082237518 Add Lenovo System Update as a Windows FMA (#50339)
**Related issue:** Resolves #50324

Adds Lenovo System Update as a Windows Fleet-maintained app, from winget
`Lenovo.SystemUpdate` (5.08.03.59, Inno Setup, machine scope, x86-only).
Found in a customer's ManageEngine ServiceDesk Plus Windows deployment
catalog with no Fleet equivalent. Distinct from
`lenovo-dock-manager/windows`, which we already ship.

## Verification

- Installer SHA confirmed against a local download of
`system_update_5.08.03.59.exe` (`e66794dc…53e0d`), served from
`download.lenovo.com` — a pinned vendor URL, so none of the SourceForge
mirror trouble from #50322 applies.
- Registry `DisplayName` determined offline as a bare `Lenovo System
Update`: `innoextract --info` reports `AppVerName` when set and falls
back to `AppName`, and Inno writes that same value to `DisplayName`.
This installer reports no version suffix, unlike CrystalDiskMark in
#50322 which reports `"CrystalDiskMark 9.0.3"`. That is why the exists
query here is an exact match rather than a prefix.
- Icon extracted from the installer's own `Tvsukernel.exe` resource, not
sourced from the web.
- The uninstall script targets the Inno registry key directly via the
manifest's `ProductCode` (`TVSU_is1` — a key name, not a GUID) using the
`$PACKAGE_ID` substitution, rather than string-matching `DisplayName`.

## Two things reviewers should weigh in on

**1. The exists query deliberately omits the publisher.** House style
usually pins `publisher = '...'`, but the registry `Publisher` is not
determinable offline for Inno, and the validator's log prints only the
name and version — so I could not confirm it. A wrong publisher makes
the exists query silently never match while the validator still passes,
which is the exact failure mode called out in the FMA docs. `name =
'Lenovo System Update'` is unambiguous on its own. Happy to add the
publisher clause if someone can confirm the registry value on a real
Lenovo host.

**2. This may not be validatable on the CI runner.** Lenovo System
Update is a vendor tool for Lenovo hardware, and the runner is a generic
Azure VM. If the installer refuses to run on non-Lenovo hardware this
will fail the way Dell Display and Peripheral Manager did in #50020
(which was dropped for exactly this reason, and is being retried on a
client-OS runner in #50313). Leaving this in draft until the validator
reports.

# Checklist for submitter

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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added Lenovo System Update to the maintained Windows software catalog.
* Added support for silent installation and uninstallation, including
status verification and reboot-success handling.
* Added Lenovo System Update metadata, download information,
categorization, and application icon.


<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 20:36:01 -05:00
kitzyandAllen Houchins 7837b8ec8f Add AOMEI Backupper Standard as a Windows Fleet-maintained app (#50021)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **AOMEI Backupper Standard** as a Windows Fleet-maintained app. One
of the 11 apps split out of #48501 that failed the FMA validator; #50016
shipped the 6 that passed.

## Why it was failing

Install and detection were already fine on the SYSTEM-context Windows
runner — the validator installed it and osquery found `AOMEI Backupper`
8.4.0. **Uninstall** was the failure:

```
ERROR msg="Error uninstalling app: exit status 1" app="AOMEI Backupper Standard"
ERROR msg="Output: Uninstaller for 'AOMEI Backupper Standard' not found."
```

AOMEI unified the ARP `DisplayName` across editions around v7.4 — the
registry entry reads `AOMEI Backupper`, with no `Standard` suffix. The
uninstall script searched for the catalog name and matched nothing.
`unique_identifier` was already corrected to `AOMEI Backupper`; this
fixes the uninstall script to match.

## Notes

- **Edition matching.** `AOMEI Backupper` also matches the paid
Pro/Workstation/Server editions — AOMEI shares the DisplayName across
editions and no registry value distinguishes them. Detecting the free
edition specifically isn't possible from inventory.
- **Non-pinned installer URL.**
`https://www2.aomeisoftware.com/download/adb/AOMEIBackupperStd.exe` is a
"latest" URL, so the pinned SHA will drift when AOMEI ships a new build
until the FMA auto-update bumps it.
- x86-only installer, so it lands in `C:\Program Files (x86)`. The
validator's "no changes detected in `C:\Program Files`" line is an
expected warning, not a failure.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30383902487](https://github.com/fleetdm/fleet/actions/runs/30383902487)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries reviewed for name + publisher
correctness, `apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added **AOMEI Backupper Standard** (version **8.4.0.0.0**) to the
Windows software catalog, including verified installer download
(SHA-256).
- Introduced dedicated silent **install** and **uninstall** support,
with version gating and idempotent uninstall behavior when the app isn’t
present.
- Added a **new software icon** and updated the listing so the correct
icon now appears for this product.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-31 09:58:02 -05:00
kitzy 43bff98998 Add Gpg4win as a Windows FMA (#50026)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **Gpg4win** as a Windows Fleet-maintained app. One of the 11 apps
split out of #48501 that failed the FMA validator; #50016 shipped the 6
that passed.

## Why it was failing

Same root cause as GNU Privacy Guard (#50025) — Gpg4win bundles GnuPG.
The install worked; the *script* never returned:

```
20:30:53  INFO  msg="Executing install script..." app=Gpg4win
20:40:53  ERROR msg="Error executing install script: exit status 1"   # exactly 10:00 later
20:40:53  INFO  msg="New application detected at: C:\Program Files\Gpg4win"
```

Ten minutes on the nose is the validator's `executeScript` timeout.
**`Start-Process -Wait` waits for the process *and all of its
descendants***, and Gpg4win leaves `gpg-agent`, `dirmngr`, `keyboxd` and
`scdaemon` resident (plus Kleopatra), so `-Wait` never returns. The same
run left `gpg4win-5.0.2.exe` locked in the validator's temp dir,
confirming a live child process.

The install script now follows the pattern already established by
[`ollama_install.ps1`](ee/maintained-apps/inputs/winget/scripts/ollama_install.ps1):
start with `-PassThru` (no `-Wait`), wait on the installer process alone
with a 7-minute cap (below the caller's 10-minute script budget), poll
for the Add/Remove Programs entry, then stop the leftovers.

The uninstall script stops those processes up front (they hold file
locks that make the uninstall fail), uses NSIS's `_?=<dir>` switch so
the uninstaller runs in place rather than relaunching itself detached
from `%TEMP%`, and polls the ARP key to confirm removal.

## Notes

- **Versioned ARP name.** The registry `DisplayName` is `Gpg4win
(5.0.2)`, so the input uses `fuzzy_match_name` and the exists query is
`name LIKE 'Gpg4win %'`. The uninstall script matches the same prefix.
- x86-only installer. Publisher `The Gpg4win Project`.
- Ships a new catalog icon and website asset.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30384125610](https://github.com/fleetdm/fleet/actions/runs/30384125610)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries reviewed for name + publisher
correctness, `apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually





<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
  - Added Gpg4win as a supported Windows application.
  - Added Gpg4win version 5.0.2 with Security categorization.
  - Added a Gpg4win icon to the software interface.
- Introduced silent install and uninstall support with process cleanup,
timeouts, and registry-based verification to confirm install/removal
outcomes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 09:57:32 -05:00
e383c42da6 Update Fleet-maintained apps (#50230)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Updated Proton Drive for Windows to version 3.0.4.
* Refreshed the installer download reference and checksum verification.
* Improved uninstall behavior by adding a pre-uninstall stop for running
Proton Drive processes and introducing a timeout-based “watchdog” to
prevent the uninstaller from hanging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-30 14:21:30 -05:00
ddf3e96fa9 Update Fleet-maintained apps (#50150)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Versions**
* Updated release metadata and installer details for AnyBurn,
BetterDisplay, Blender, ChatGPT, ChatWise, CLion, DataGrip, Dataspell,
Draw.io Desktop, Elgato Camera Hub, Firefox, Firefox Nightly, GIMP,
GoLand, IntelliJ IDEA (and variants), Keka, Kitty, LibreOffice,
MacPacker, MuseScore, Nudge, OBS, PhpStorm, PyCharm (CE), Rider,
RubyMine, RustRover, Spyder, Typinator, Visual Studio Code, VLC,
WebStorm, Wispr Flow, XLD, and others.
  * Refreshed app version detection and package checksums accordingly.

* **Bug Fixes**
* Improved uninstall reliability on macOS and Windows by removing
unintended extra cleanup actions while keeping application and user-data
removal.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-29 10:55:36 -05:00
kitzy f4d7064f9a Add Rtools as a Windows FMA (#50028)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **Rtools** as a Windows Fleet-maintained app. One of the 11 apps
split out of #48501 that failed the FMA validator; #50016 shipped the 6
that passed.

## Why it was failing

The install script hit the validator's 10-minute `executeScript` cap
exactly:

```
20:41:22  INFO  msg="Executing install script..." app=Rtools
20:51:22  ERROR msg="Error executing install script: exit status 1"   # exactly 10:00 later
20:51:22  WARN  msg="failed to remove rtools45-6768-6492.exe: ... Access is denied."
```

The locked installer in the temp dir shows a process was still alive.
`Start-Process -Wait` waits for the process *and all of its
descendants*, which is the same root cause as the other install-timeout
apps in this batch.

Rtools is also the one app in the batch where a **slow unpack** is a
plausible second cause — the installer is ~460 MB and expands a full
toolchain. So rather than assume, the script now waits on the installer
process alone with a 480s cap (under the caller's 10-minute budget) and
logs elapsed time plus Add/Remove Programs registration state on every
poll. If the cap is reached:

- **registered** → the install finished and only a lingering child
remains, so it stops that process and succeeds;
- **not registered** → the unpack genuinely didn't finish, and it fails
with that stated explicitly.

Either way the CI log now says which one happened instead of just timing
out.

## Notes

- **Identity verified against the installer**, not winget metadata. The
setup stub's PE version resource reads `CompanyName: The R Foundation`,
`ProductName: Rtools`. Inno derives `VersionInfoCompany` from
`AppPublisher`, so the ARP publisher is `The R Foundation` — which is
what the exists query uses.
- **Versioned ARP name.** The registry `DisplayName` is `Rtools 4.5
(6768-6492)`, so the input uses `fuzzy_match_name` and the exists query
is `name LIKE 'Rtools %'`.
- Installs to `C:\rtools45`, not Program Files, so the validator's "no
changes detected in `C:\Program Files`" line is an expected warning, not
a failure.
- Ships a new catalog icon and website asset.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30384196159](https://github.com/fleetdm/fleet/actions/runs/30384196159)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries checked against the installer's PE
version resource, `apps.json` is valid JSON with a description filled
in.
- [x] QA'd all new/changed functionality manually




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added Rtools as a supported Windows application.
* Added installation and uninstallation support with silent setup and
silent removal.
* Added Rtools version metadata, installer verification, and Developer
tools categorization.
  * Added a dedicated Rtools icon for software listings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-28 23:01:31 -05:00
kitzy ea3f95ccc5 Add Google Earth Pro as a Windows FMA (#50022)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **Google Earth Pro** as a Windows Fleet-maintained app (a Windows
counterpart to the existing `google-earth-pro/darwin` FMA). One of the
11 apps split out of #48501 that failed the FMA validator; #50016
shipped the 6 that passed.

## Why it was failing

Install and detection were already fine on the SYSTEM-context Windows
runner — osquery found `Google Earth Pro` 7.3.7.1155. **Uninstall** was
the failure:

```
INFO  msg="Executing uninstall script for app..." app="Google Earth Pro"
INFO  msg="Found app: 'Google Earth Pro' at , Version: 7.3.7.1155"
ERROR msg="App still present after uninstall (expected no match for version '7.3.7.1155' in programs)"
```

The EXE wraps a WiX MSI, so the ARP `UninstallString` is `MsiExec.exe
/X{ProductCode}` — with **no quiet switch**. The old script ran that
string verbatim, which raises a confirmation dialog in session 0 where
nothing can click it, so the uninstall silently no-ops.

The uninstall script now resolves the MSI product code (from the
`UninstallString`, falling back to the registry key name) and runs
`msiexec /x <code> /quiet /norestart` with a bounded 5-minute wait, then
drains child `msiexec` processes. `3010`/`1641` are treated as success.

## Notes

- **Identity verified against the real installer**, not winget metadata.
The installer's embedded MSI Property table reads: `ProductName` =
`Google Earth Pro`, `Manufacturer` = `Google`, `ProductVersion` =
`7.3.7.1155`, `ALLUSERS` = `1`, `ProductCode` =
`{E3B69BB6-FFD8-441C-933E-BB8A3136ED8F}`. No `ARPSYSTEMCOMPONENT`, so it
is not a bootstrapper. That confirms `unique_identifier` = `Google Earth
Pro` and the exists-query publisher `Google` (not "Google LLC").
- Installs to `C:\Program Files (x86)`, so the validator's "no changes
detected in `C:\Program Files`" line is an expected warning, not a
failure.
- Reuses the existing `google earth pro` catalog icon — no new icon
needed.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30383934805](https://github.com/fleetdm/fleet/actions/runs/30383934805)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries checked against the MSI Property table,
`apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added Google Earth Pro for Windows to the maintained app catalog.
* Added maintained-apps install and uninstall support for Google Earth
Pro (version 7.3.7.1155).
* Included automated download integrity verification and detection logic
for installed/updated versions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-28 22:59:39 -05:00
kitzy c61632305a Add Logitech Unifying Software as a Windows FMA (#50024)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **Logitech Unifying Software** as a Windows Fleet-maintained app.
One of the 11 apps split out of #48501 that failed the FMA validator;
#50016 shipped the 6 that passed.

## Why it was failing

Install and detection were already fine on the SYSTEM-context Windows
runner — osquery found `Logitech Unifying Software 2.52` at `C:\Program
Files\Common Files\LogiShrd\Unifying`. **Uninstall** was the failure:

```
20:40:55  INFO  msg="Executing uninstall script for app..."
20:40:57  INFO  msg="Found app: 'Logitech Unifying Software 2.52' ... Version: 2.52.33"
20:40:57  ERROR msg="App still present after uninstall (expected no match for version '2.52.33' in programs)"
```

Two seconds start to finish — the uninstaller hadn't actually done
anything yet. This is standard NSIS behavior: the uninstaller copies
itself to `%TEMP%` and relaunches, so the process the script starts
exits almost immediately while the real work happens in a detached
child.

The fix passes NSIS's `_?=<dir>` switch, which runs the uninstaller in
place instead of relaunching, making it synchronous. It has to be the
last argument and unquoted, so the script builds a single argument
string rather than an array (PowerShell would quote an element
containing spaces). A bounded poll on the ARP key follows as a backstop,
and the script fails explicitly if the entry is still there.

## Notes

- **Versioned ARP name.** The registry `DisplayName` is `Logitech
Unifying Software 2.52`, so the input uses `fuzzy_match_name` and the
exists query is `name LIKE 'Logitech Unifying Software %'`. The
uninstall script matches the same prefix rather than an exact string.
- Publisher `Logitech` confirmed against the winget locale manifest.
- Installs under `C:\Program Files\Common Files`, so the validator's "no
changes detected in `C:\Program Files`" line is an expected warning, not
a failure.
- Ships a new catalog icon and website asset.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30384010810](https://github.com/fleetdm/fleet/actions/runs/30384010810)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries reviewed for name + publisher
correctness, `apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Logitech Unifying Software to the Windows software catalog,
including the version 2.52.33 download, checksum, and install-detection
metadata.
* Implemented silent installation and a robust, registry-aware uninstall
flow (with process lock handling and timeout behavior).
* Added a dedicated Logitech Unifying Software icon to the software page
UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-28 22:59:09 -05:00
kitzy cfbb5a59fa Add GNU Privacy Guard as a Windows FMA (#50025)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **GNU Privacy Guard** as a Windows Fleet-maintained app. One of the
11 apps split out of #48501 that failed the FMA validator; #50016
shipped the 6 that passed.

## Why it was failing

The install itself worked — the validator logged `New application
detected at: C:\Program Files\GnuPG`. The *script* never returned:

```
20:18:36  INFO  msg="Executing install script..." app="GNU Privacy Guard"
20:28:36  ERROR msg="Error executing install script: exit status 1"   # exactly 10:00 later
20:28:36  INFO  msg="New application detected at: C:\Program Files\GnuPG"
```

Ten minutes on the nose is the validator's `executeScript` timeout. The
cause is a PowerShell detail rather than anything wrong with the
installer: **`Start-Process -Wait` waits for the process *and all of its
descendants***. GnuPG's installer starts `gpg-agent`, `dirmngr`,
`keyboxd` and `scdaemon` and leaves them resident, so `-Wait` never
returns. The same run left the installer `.exe` locked in the
validator's temp dir, which is the other tell that a child process was
still alive.

The install script now follows the pattern already established by
[`ollama_install.ps1`](ee/maintained-apps/inputs/winget/scripts/ollama_install.ps1):
start with `-PassThru` (no `-Wait`), wait on the installer process alone
with a 7-minute cap (below the caller's 10-minute script budget), poll
for the Add/Remove Programs entry so a fast-returning installer can't be
mistaken for a finished one, then stop the daemons.

Stopping the daemons also fixes the uninstall, which would otherwise
fail on files those processes hold open. The uninstall script stops them
up front, uses NSIS's `_?=<dir>` switch so the uninstaller runs in place
instead of relaunching itself detached from `%TEMP%`, and polls the ARP
key to confirm removal.

## Notes

- Clean ARP `DisplayName` (`GNU Privacy Guard`), so exact name matching
— no `fuzzy_match_name` needed. Publisher `The GnuPG Project`.
- Ships a new catalog icon and website asset.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30384069714](https://github.com/fleetdm/fleet/actions/runs/30384069714)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries reviewed for name + publisher
correctness, `apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually





<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added GNU Privacy Guard as a supported Windows application in the
maintained apps catalog.
  * Added install/upgrade detection and uninstall support for Windows.
  * Added GNU Privacy Guard to the software catalog (Security category).
* Added a dedicated GNU Privacy Guard icon to the software interface for
proper name-based display.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-28 22:56:46 -05:00
kitzy 2fc41c7592 Add Azure Data Studio as a Windows FMA (#50027)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #50020

# What this does

Adds **Azure Data Studio** as a Windows Fleet-maintained app. One of the
11 apps split out of #48501 that failed the FMA validator; #50016
shipped the 6 that passed.

## Why it was failing

The install itself worked — the validator logged `New application
detected at: C:\Program Files\Azure Data Studio`. The *script* never
returned:

```
20:08:19  INFO  msg="Executing install script..." app="Azure Data Studio"
20:18:19  ERROR msg="Error executing install script: exit status 1"   # exactly 10:00 later
20:18:19  INFO  msg="New application detected at: C:\Program Files\Azure Data Studio"
```

Ten minutes on the nose is the validator's `executeScript` timeout.
Azure Data Studio is a Visual Studio Code fork and ships the same Inno
Setup script — including the **`runcode` task, which launches the app
when the install finishes**. Because `Start-Process -Wait` waits for the
process *and all of its descendants*, the launched app kept the script
blocked forever.

The fix is the switch VS Code's own FMA already uses:
`/MERGETASKS=!runcode` (see
[`vscode_install.ps1`](ee/maintained-apps/inputs/winget/scripts/vscode_install.ps1)
and
[`vscodium_install.ps1`](ee/maintained-apps/inputs/winget/scripts/vscodium_install.ps1),
both of which pass validation). The script also waits on the installer
process alone rather than its descendants, polls for the Add/Remove
Programs entry, and stops a stray `azuredatastudio` process as a
backstop in case a future build ignores the task suppression.

## Notes

- Machine-scope x64 installer, per the winget manifest — Azure Data
Studio publishes both user and machine scope, and Fleet installs run as
SYSTEM, so machine scope is required.
- Clean ARP `DisplayName` (`Azure Data Studio`), so exact name matching.
Publisher `Microsoft Corporation`.
- Uninstall is unchanged: the Inno uninstaller doesn't leave anything
resident, and `-Wait` waiting on descendants is the desired behavior
there (Inno relaunches itself from `%TEMP%`).
- Ships a new catalog icon and website asset.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] FMA CI validator (install → detect → uninstall) **passes** on the
SYSTEM-context Windows runner — [run
30384162280](https://github.com/fleetdm/fleet/actions/runs/30384162280)
(`All checks passed`)
- [x] Generated output verified locally: manifest SHA matches the winget
manifest, exists/patched queries reviewed for name + publisher
correctness, `apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added Azure Data Studio to the available Windows software catalog.
* Added support for installing and uninstalling Azure Data Studio
(version 1.52.0) via silent installer and uninstaller flows with
completion detection.
* Added an Azure Data Studio icon to the software interface for better
visual identification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-28 22:56:00 -05:00
95008e853d Freeze the macOS Yubikey Manager FMA (#50054)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** N/A

Pins the macOS Fleet-maintained app for Yubikey Manager at its current
output version, `1.2.5`, by setting `"frozen": true` on the Homebrew
input.

- `ee/maintained-apps/inputs/homebrew/yubico-yubikey-manager.json` —
added `"frozen": true`

The flag is read by the ingester into the manifest
([`homebrew/ingester.go`](https://github.com/fleetdm/fleet/blob/main/ee/maintained-apps/ingesters/homebrew/ingester.go))
and then consumed in two places:

-
[`cmd/maintained-apps/main.go`](https://github.com/fleetdm/fleet/blob/main/cmd/maintained-apps/main.go)
skips overwriting `outputs/yubico-yubikey-manager/darwin.json` when the
output file already exists, so the published version, installer URL,
SHA256, and patch-policy query stop moving.
-
[`cmd/maintained-apps/validate/main.go`](https://github.com/fleetdm/fleet/blob/main/cmd/maintained-apps/validate/main.go)
skips the app entirely during validation.

`frozen` is tagged `json:"-"` on the manifest struct, so it does not
appear in the output JSON — that file is intentionally left untouched by
this PR.

The Windows FMA (`inputs/winget/yubico-yubikey-manager.json`) is
deliberately **not** frozen and continues to update.

## Note for reviewers

While preparing this change I found that the `yubico-yubikey-manager`
cask has been **removed from homebrew-cask**:

```
https://formulae.brew.sh/api/cask/yubico-yubikey-manager.json → 404
```

The full cask list now contains only `pallotron-yubiswitch`,
`yubico-authenticator`, and `yubihsm2-sdk`, and the token is not present
in `inputs/homebrew/custom-tap/` either. This matches upstream: Yubico
discontinued `yubikey-manager-qt` (the 1.2.5 installer this FMA pins) in
favor of Yubico Authenticator.

**Freezing does not resolve that 404.** The ingester fetches cask
metadata *before* the frozen flag is applied, so the scheduled "Update
Fleet-maintained apps" workflow is still expected to fail for this app
with:

```
panic: ingesting homebrew app: app not found in brew API
```

Fully resolving that would mean removing the macOS FMA, the way #50050
did for Nocturnal (input, output, `apps.json` entry, frontend icon +
mapping, website PNG). This PR is scoped to the freeze only, per the
request; the removal can be a follow-up if that is the direction we
want.

# Checklist for submitter

- [x] 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

- [x] QA'd all new/changed functionality manually — confirmed the edited
input still parses as JSON, and traced the `frozen` flag through the
ingester into both the output-write skip and the validation skip. No
automated tests were added; this is a single data-file flag with no new
code paths.

---------

Co-authored-by: Kitzy <kitzy@kitzy.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-28 10:57:18 -05:00
kitzyandAllen Houchins 3ce3e238ed Add 6 Windows Fleet-maintained apps (#50016)
**Related issue:** N/A — Windows Fleet-maintained app (FMA) coverage for
apps found deployed in a customer's ManageEngine SDP environment but
missing from Fleet.

## What this does

Adds **6** Windows Fleet-maintained apps — the subset of a larger batch
that passes the FMA validator cleanly. Each has a winget-sourced input,
a generated output manifest, and a catalog icon. Detection identity was
verified against each app's real registry DisplayName; apps whose
DisplayName carries a version suffix use fuzzy name matching, the rest
match exactly.

**MSI (clean, auto upgrade-code uninstall):**
- **Git Extensions** — versioned ARP name (`Git Extensions 7.2.0.92`) →
fuzzy match
- **TightVNC**, **Yarn**, **SonicWall NetExtender** (WiX), **Zoom
Outlook Plugin** — clean ARP names → exact match

**EXE — NSIS (custom `/S` install + registry-lookup uninstall):**
- **Spyder** — versioned ARP name (`Spyder 6`) → fuzzy match

## Notes

- **Detection verification.** Every app's `unique_identifier` (registry
DisplayName / osquery `programs.name`) and publisher were verified per
the `new-fma` skill against winget `AppsAndFeaturesEntries`, MSI
Property tables (`msiinfo`), and vendor installer scripts — not assumed.
Git Extensions' MSI `ProductName` is `Git Extensions 7.2.0.92` and
Spyder's ARP entry is `Spyder 6`, so both need `fuzzy_match_name`; the
four exact-match apps were confirmed clean (e.g. TightVNC registers as
`TightVNC`, not a versioned string).
- **Validated on a real Windows host.** All six pass the FMA CI
validator (install → detect → uninstall) on the SYSTEM-context Windows
runner.
- **Icons.** Git Extensions, SonicWall NetExtender, TightVNC, Yarn, and
Zoom Outlook Plugin ship new catalog icons + website assets; Spyder
reuses the existing `Spyder` icon.

## Testing

- [x] FMA CI validator (install → detect → uninstall) on the
SYSTEM-context Windows runner.
- Generated outputs verified locally: all 6 produce valid manifests; MSI
apps carry the correct UpgradeCode-based uninstall; exists/patched
queries reviewed for name + publisher correctness; `go test
./ee/maintained-apps/...` passes.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added maintained Windows catalog entries for Git Extensions, SonicWall
NetExtender, Spyder, TightVNC, Yarn, and Zoom Outlook Plugin, including
silent install, version upgrade detection, and maintenance-ready
uninstall flows.
* Added new software icons for these apps and expanded icon matching so
they display correctly in the catalog.
* **Bug Fixes**
* Improved Spyder Windows uninstall targeting and command/argument
handling for more reliable removals.
* **Documentation**
  * Refreshed Spyder supported version details to 6.1.5.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-28 10:14:53 -05:00
Allen Houchins f96cb9d6c7 Add Windows FMAs (letter I): 17 apps (#49923)
**Related issue:** N/A — part of the ongoing Windows Fleet-maintained
apps (FMA) parity workstream (letter I).

## What this does

Adds **17** Windows Fleet-maintained apps for the letter-I batch.
apps.json descriptions follow the house `"<Name> is a …"` convention.

**IBM Semeru Runtime Open Edition (Java) — 8 apps** (MSI, machine, x64):
JDK 8/11/17/21 and JRE 8/11/17/21. Per-major detection follows the
Eclipse Temurin pattern — `name LIKE 'IBM Semeru Runtime Open Edition
(JDK|JRE)%' AND publisher = 'Semeru' AND version LIKE '<major>.%'` — so
majors and JDK/JRE never cross-match. All 8 share the IBM Semeru logo.

**Other apps (9):**
- **IronPython 3** — MSI; fuzzy `IronPython 3%` (excludes the EOL v2)
- **ImageGlass** — dual-scope MSI, custom `ALLUSERS=1` install
- **install4j** — install4j installer (bundled JRE), `-q`
- **IrfanView** — custom installer `/silent /allusers=1`; versioned+arch
name → fuzzy `IrfanView%`
- **iMazing HEIC Converter**, **IsoBuster**, **ImpExpPro** — Inno Setup
(`/VERYSILENT`)
- **Infix PDF Editor** — Inno, x86
- **Ibis Calculeren voor Bouw** — InstallShield wrapper; uninstall via
the MSI UpgradeCode (`uninstall_type: msi`)

## Dropped from this batch (recorded in the workstream tracker)
- **IBM Aspera Connect** — MSI installs per-user by default
(`ALLUSERS=2`/`MSIINSTALLPERUSER=1`, no machine switch in the manifest)
+ a rolling `/latest/` URL that 404s on the next release.
- **IcedTea-Web** (Azul and AdoptOpenJDK) — both declare a hard,
unbundled JRE dependency; it's a JNLP launcher that's dead-on-arrival
without Java the FMA won't install (AdoptOpenJDK variant is also
archived).
- **IronPython 2** — EOL (Python 2). **install4j 9** — legacy 2022
build. **IrfanView PlugIns** — add-on that drops DLLs with no
independent ARP entry (undetectable) and depends on the base app.
- **Intermedia Unite** and its **Teams Desktop Plugin** — both use
non-versioned "latest" URLs (winget-bot auto-updated; the pinned SHA
rots each release); the plugin also depends on Teams + Unite.

## Notes
- **Ibis Calculeren voor Bouw**, **ImpExpPro**, and **Infix PDF Editor**
ship without a catalog icon — no clean ≥256px first-party logo exists
for these niche apps (they fall back to the generic icon).
- **Ibis** carries some risk: its DisplayName couldn't be verified
offline (compressed InstallShield payload) and its installer URL is
non-versioned — flagging for the validator; will drop if
detection/version fails.
- **IsoBuster** uses a non-versioned URL that currently matches the
pinned 5.8, so no `ignore_hash`; winget re-ingestion tracks future
drift.
- Verification (installer type/scope/arch, ProductCode/UpgradeCode,
dependencies, silent switches, URL stability, en-US locale) was done per
the `new-fma` skill against the winget-pkgs manifests, with several
DisplayNames confirmed via `innoextract`/`msitools`.

## Testing
- [ ] FMA CI validator (install → detect → uninstall) on the
SYSTEM-context Windows runner — pending.
- Generated outputs verified locally: all 17 produce valid manifests;
per-major Semeru queries and MSI UpgradeCode uninstalls confirmed; all
apps.json descriptions present and convention-compliant.
2026-07-24 21:46:12 -05:00
d138998d84 Update Fleet-maintained apps (#49857)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added frozen status to Pritunl package configurations, preventing
automatic version changes.

* **Updates**
* Refreshed maintained application packages across Windows and macOS,
including Firefox, Bitwarden, Postman, Arc, Granola, PhpStorm, WebStorm,
Bruno, and others.
* Updated download links and integrity checks to match the latest
available installers.
* Improved version detection so upgrades are recognized correctly for
each refreshed release.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-23 16:34:35 -05:00
707199656d Update Fleet-maintained apps (#49813)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added/updated managed release definitions for many applications on
both macOS and Windows.
* Updated the Mozilla VPN installer script to better handle MSI installs
in automation.
* Marked Typora for frozen behavior in winget to prevent unintended
updates.
* **Bug Fixes**
* Refreshed download links and integrity checksums across updated app
versions.
  * Improved version detection for newly released builds.
* **Improvements**
* Enhanced uninstall behavior for select apps (additional
cleanup/services and improved media handling for ONLYOFFICE macOS).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-23 14:28:11 -05:00
Allen Houchins 369995037c Add Airtable Fleet-maintained app for Windows (#49795)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49736

Adds **Airtable** (`Formagrid.Airtable`) as a Fleet-maintained app for
Windows, grouped with the existing macOS Airtable FMA under the shared
name `Airtable`.

## What changed

- `ee/maintained-apps/inputs/winget/airtable.json` — winget input
(`neutral` arch, `exe`, `user` scope).
- `ee/maintained-apps/inputs/winget/scripts/airtable_install.ps1` —
install script.
- `ee/maintained-apps/inputs/winget/scripts/airtable_uninstall.ps1` —
uninstall script.
- `ee/maintained-apps/outputs/airtable/windows.json` — generated output.
- `ee/maintained-apps/outputs/apps.json` — catalog entry + description.

## Reviewer notes

- **Identity verified against the real installer, not the catalog.**
Downloaded `AirtableSetup1.4.5.exe` (SHA256 matched the winget manifest
exactly) and extracted the embedded Squirrel nuspec:
`<title>Airtable</title>` → registry DisplayName `Airtable`
(`unique_identifier`); `<authors>Airtable</authors>` → registry
Publisher `Airtable`. Publisher matches the winget locale, so no
`program_publisher` override is needed. exists query: `... WHERE name =
'Airtable' AND publisher = 'Airtable'`.
- **Per-user Squirrel installer.** Manifest is `InstallerType: exe`,
`Scope: user`, `Silent: --silent` (Squirrel signature). Run directly as
Local System it would install into the SYSTEM profile and lock its own
`.exe`, so the install uses the **Figma/Postman scheduled-task pattern**
— it runs `--silent` in the logged-on user's session. Uninstall does a
defensive HKLM+HKCU registry lookup and runs Squirrel's `Update.exe
--silent`.
- `installer_arch: neutral` matches the manifest (Dynalist precedent).
The binary is x86; it runs on x64 and ARM64 via emulation and routes to
`windows-latest`.
- Installer URL is version-pinned (`AirtableSetup1.4.5.exe`), not a
"latest" redirect, so the pinned SHA won't silently drift.
- Icon already exists (frontend `airtable` key + website PNG). No shared
Go code changed; no validator rebuild needed.

# Checklist for submitter

- [x] QA'd all new/changed functionality manually (winget manifest +
installer identity verified; output regenerated via `go run
cmd/maintained-apps/main.go --slug="airtable/windows"`; `apps.json`
valid JSON, additions-only diff)

<!-- FMA-only content change: no changes/ entry, DB migration, config
setting, or fleetd change. -->
2026-07-22 17:02:13 -05:00
Allen Houchins 568fc0edb8 Add Podman Desktop Fleet-maintained app for Windows (#49790)
**Related issue:** Resolves #49737

Adds **Podman Desktop** (`RedHat.Podman-Desktop`, v1.28.3) as a Windows
Fleet-maintained app. It pairs with the existing `podman-desktop/darwin`
FMA under the same catalog name, so the two group together in the FMA
library and share the existing icon.

## What changed

- `ee/maintained-apps/inputs/winget/podman-desktop.json` — input
manifest (x64, machine scope, `exe`/NSIS)
- `ee/maintained-apps/inputs/winget/scripts/podman-desktop_install.ps1`
/ `_uninstall.ps1` — custom install/uninstall scripts
- `ee/maintained-apps/outputs/podman-desktop/windows.json` — generated
output
- `ee/maintained-apps/outputs/apps.json` — new Windows catalog entry;
also reworded the existing macOS description to the standard "`<App>` is
a(n)…" format for consistency

## Identity verification (verified, not guessed)

Podman Desktop ships as an **electron-builder NSIS** installer, and the
winget metadata does **not** match what osquery sees on a host. I
decompressed the NSIS header and traced electron-builder's source to
confirm the registry identity:

- **DisplayName** = `Podman Desktop 1.28.3` → `unique_identifier:
"Podman Desktop"` with `fuzzy_match_name` (matches the version-suffixed
name).
- **Publisher** = `Podman Desktop` — derived from the package's
`author.name` (`COMPANY_NAME` → registry `Publisher`), **not** the
winget locale's `RedHat`. Using "RedHat" would have made the exists
query silently never match on real hosts.
- **SHA256** matches the winget manifest exactly; version `1.28.3`
equals the registry `DisplayVersion`, so the patch policy reconciles
cleanly.

Silent install uses `/S /ALLUSERS`; uninstall does a registry lookup
with the defensive three-shape UninstallString parser and mirrors `/S
/ALLUSERS`, following the proven `another-redis-desktop-manager`
electron-builder machine-scope pattern.

## Reviewer notes

- **x64 only.** The winget manifest also has an arm64 installer, but the
repo has no arm64 FMA inputs yet (x64 is the established convention). A
separate arm64 slug can be added later.
- Installer URL is a **pinned GitHub release** asset (not a "latest"
redirect), so the SHA is stable until the FMA auto-updater bumps the
version.
- No shared Go code changed — only FMA input/output data.

## Checklist for submitter

- [x] 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

- [x] Generated the output via `go run cmd/maintained-apps/main.go
--slug="podman-desktop/windows" --debug`; verified exists/patched
queries, pinned SHA matches the winget manifest, and `apps.json` is
valid JSON.
- [ ] QA'd install/uninstall on a Windows host (will be exercised by the
FMA validator).
2026-07-22 16:24:26 -05:00
Allen Houchins 1a1b6e786a Add Box Tools Fleet-maintained app for Windows and macOS (#49779)
**Related issue:** Resolves #49738

Adds **Box Tools** as a Fleet-maintained app for **Windows** (winget
`Box.BoxTools`) and **macOS** (Homebrew cask `box-tools`).

## Windows details (verified against the real installer)

- Installer: `BoxToolsInstaller-AdminInstall.msi` (machine-wide admin
MSI, `ALLUSERS=1`), x64.
- MSI Property table verified with msitools: `ProductName` = `Box
Tools`, `Manufacturer` = `Box`, `ProductVersion` = `4.32.0.1324` — all
match the winget manifest, so `unique_identifier: "Box Tools"` and the
generated exists query (`name = 'Box Tools' AND publisher = 'Box'`)
match what osquery reports from the registry. No `program_publisher`
override needed.
- The winget manifest has no `Scope`, so `installer_scope: "machine"` is
set in the input (confirmed by `ALLUSERS=1`).
- ProductCode/UpgradeCode match the manifest; standard auto-generated
machine-MSI install + upgrade-code uninstall scripts.
- Downloaded SHA256 matches the manifest SHA (`9f5958...c5066`).
- No sibling-name collision with the existing Box Drive FMA (its
DisplayName is `Box`; matching is exact).

## macOS details

Box Tools on macOS is **per-user only**: the cask installs the app
bundles to `~/Library/Application Support/Box/Box Edit/`, Box's admin
`.pkg` forbids the local system domain (`enable_localSystem="false"`),
and [Box's large-scale deployment
docs](https://support.box.com/hc/en-us/articles/360043695834-Large-Scale-Deployments-Box-Tools)
instruct running the installer as the console user (multi-user Macs are
not a supported configuration).

Because the install location is outside osquery's `apps` directory scan
paths, detection relies on osquery's LaunchServices enumeration
(`_LSCopyAllApplicationURLs` in `genApps`), which surfaces the bundles
regardless of location — verified against a live install where all four
Box Tools bundles are LaunchServices-registered at the per-user path and
appear in the `apps` table.

- `unique_identifier: com.Box.Box-Edit` (verified `CFBundleIdentifier`
of `Box Edit.app` in the DMG).
- **Custom install script** (the cask's app artifacts are all
`target:`-style, which the script generator skips, so the auto-generated
script would be a no-op): resolves the console user (falling back to
`lastUserName` when run while logged out), quits the Box apps, copies
the four app bundles from the DMG's `Install Box
Tools.app/Contents/Resources/` into the user's `~/Library/Application
Support/Box/Box Edit/` (replicating the cask/pkg payload exactly),
chowns them, and registers them with LaunchServices in both root and
user contexts so inventory and box.com pick them up without a first
manual launch.
- **Custom uninstall script**: quits/kills the Box Tools processes and
removes `Box Edit` from every local user's home. The parent `Box`
directory is shared with Box Drive, so it is only removed if left empty.
- The cask uses `sha256 :no_check` (rolling `currentrelease` URL) — the
established FMA convention for such casks (same as Google Chrome/VS Code
darwin); the server pins the hash of what it downloads at add time.
- Cask version is `4.32` while `CFBundleShortVersionString` is `4.32.0`;
the validator's prefix matching and the patched query's
`version_compare` both treat these as consistent.

## Shared caveat

⚠️ Both installer URLs are rolling `currentrelease` links. On Windows
the pinned SHA will drift when Box ships a new build until the FMA
auto-update ingests the new manifest; macOS uses `no_check` per the
cask.

## Icon

Generated from the official 512×512 Box Tools icon shipped inside the
vendor DMG via `tools/software/icons/generate-icons.sh` (frontend
`BoxTools.tsx` + map entry, website `app-icon-box-tools-60x60@2x.png`).
Both platforms share the icon via the common `name`/slug.

# Checklist for submitter

- [x] 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

- [x] Verified installer identity fields (MSI Property table, Info.plist
bundle identifiers/versions) with msitools/PlistBuddy against the live
installers; Windows output SHA matches the winget manifest.
- [x] Verified on a live macOS install that the per-user Box Tools
bundles are LaunchServices-registered and visible to osquery's `apps`
table.
- [ ] FMA validation CI (Windows + macOS runners) to confirm
install/uninstall/detection.
- [ ] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added Box Tools support for both Windows and macOS.
* Added Box Tools install, upgrade, and uninstall handling for the
maintained app catalog.
* Published Box Tools metadata to the software catalog (including
platform-specific entries and version targeting).
* Added a Box Tools icon to the software interface for improved name
matching.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 15:41:21 -05:00
Allen Houchins a3f6a45fa9 Add Windows FMAs (letter H): 5 apps (#49478)
**Related issue:** N/A — part of the ongoing Windows Fleet-maintained
apps (FMA) parity workstream (letter H).

## What this does

Adds **5** Windows Fleet-maintained apps for the letter-H batch, each
with a winget-sourced input, generated output manifest, and a
first-party catalog icon.

- **HashTools** — Inno Setup, machine, x64. File-checksum utility
(Binary Fortress). Has an evergreen WebView2 dependency (present on
virtually all fleets; core hashing works without it).
- **HeidiSQL** — Inno Setup, machine, x64. Custom install passes
`/ALLUSERS` for machine scope (the ingester doesn't forward manifest
`Custom` switches).
- **HWMonitor** — Inno Setup, machine, x86. ARP DisplayName is `CPUID
HWMonitor`; installs a kernel driver removed by its uninstaller.
- **HP Prime Virtual Calculator** — WiX burn bundle, machine, x64. Uses
a name-only exists query (the ARP publisher is MORAVIA Consulting, not
the manifest's "HP").
- **Huddle** — InstallShield exe (`/exenoui /quiet`), machine, x86.
Uninstall via the MSI UpgradeCode (`uninstall_type: msi`); `ignore_hash`
for the non-versioned installer URL.

## Dropped from this batch (recorded in the workstream tracker)
- **HandBrake** — the GUI declares an unbundled
`Microsoft.DotNet.DesktopRuntime.10` dependency (won't run without it);
the CLI is a portable zip with no Add/Remove Programs entry to detect.
- **HiPIN** (`KPN.HIPIN`) — unbundled VCRedist + WebView2 dependencies,
a non-versioned "latest" URL, and no ARP DisplayName/ProductCode. The
`binbat.whipinto` candidate is an unrelated WebRTC tool (mismatch).

## Notes
- **Huddle** uses a non-versioned `HuddleSetup.exe` URL — if the vendor
ships past 4.8.0.0, osquery version detection may drift (same pattern
that failed GoodSync in letter G). Flagging for the validator; will drop
if it can't match.
- Verification (winget manifest identity, installer type/scope/arch,
ProductCode/UpgradeCode, silent switches, dependencies, URL stability)
was done per the `new-fma` skill against the winget-pkgs manifests.

## Testing
- [ ] FMA CI validator (install → detect → uninstall) on the
SYSTEM-context Windows runner — pending.
- Generated outputs verified locally: all 5 produce valid manifests;
exists/patched queries reviewed; Huddle's MSI UpgradeCode uninstall
auto-generated correctly.
2026-07-22 14:34:36 -05:00
Allen Houchins cbcc3a8855 Fix Portfolio Performance FMA ingestion after winget scope correction (#49538)
**Related issue:** N/A — fixes the nightly "Update Fleet-maintained
apps" workflow failure on 2026-07-19 (`panic: ingesting winget app:
failed to find installer for app`).

## Details

The `buchen.portfolio` 0.86.0 winget manifest (released 2026-07-16)
changed the top-level `Scope` from `machine` to `user`. Our input pinned
`installer_scope: machine`, so the ingester's installer-matching loop
found no candidate and panicked, killing the whole nightly
maintained-apps run.

The upstream change was a **correction**, not a mistake: the vendor's
NSIS installer script (`portfolio-product/installer/installer.nsi`)
installs to `$LOCALAPPDATA\Programs` and registers under HKCU, and is
byte-identical between 0.85.0 and 0.86.0 — the installer has been
user-scoped all along; prior winget manifests mislabeled it.

Changes:
- `ee/maintained-apps/inputs/winget/portfolioperformance.json`:
`installer_scope` → `user` (52 other winget inputs already use user
scope)
- `ee/maintained-apps/outputs/portfolioperformance/windows.json`:
regenerated via `go run ./cmd/maintained-apps -slug
portfolioperformance/windows` — version 0.85.0 → 0.86.0, installer URL
and sha256 updated (sha256 matches the manifest's declared
`InstallerSha256`)

No behavior change for hosts: the custom uninstall script already
searches HKCU first, osquery's `programs` table reads per-user (HKU)
uninstall keys so the exists/patched queries keep working, and the
ingester's MSI-only scope branches don't apply to this exe-type app with
custom scripts.

# 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.

- [x] 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

- [x] QA'd all new/changed functionality manually — verified the 0.86.0
winget manifest and vendor NSIS script upstream, and regenerated the
output locally with the ingester (previously panicking, now succeeds).
2026-07-18 21:21:58 -05:00
Allen Houchins 0e587dc80c Add Firefox Nightly and Firefox Developer Edition Fleet-maintained apps (#49501)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #49149

Adds **Firefox Nightly** and **Firefox Developer Edition** as
Fleet-maintained apps for macOS and Windows (4 new FMAs). Mozilla VPN
(also requested in #49149) is not included, so this doesn't close the
issue.

## What's included

| App | macOS | Windows |
|-----|-------|---------|
| Firefox Nightly | Homebrew cask `firefox@nightly`, bundle ID
`org.mozilla.nightly` | MSIX `Mozilla.Firefox.Nightly.MSIX` (no exe
package exists in winget) |
| Firefox Developer Edition | Homebrew cask `firefox@developer-edition`,
bundle ID `org.mozilla.firefoxdeveloperedition` | NSIS exe
`Mozilla.Firefox.DeveloperEdition`, machine scope, `/S` |

All identity fields were verified against the real installers, not
catalog metadata: bundle IDs read from the DMGs' `Info.plist`, MSIX
identity ("Firefox Nightly" / publisher "Mozilla Corporation") read from
the package's `AppxManifest.xml`, and DevEd's registry identity
(`Firefox Developer Edition (x64 en-US)`, publisher `Mozilla`,
versionless DisplayName) confirmed in Mozilla's NSIS installer source at
the shipped release tags.

The MSIX install/uninstall scripts follow the established Slack/Claude
pattern (provision machine-wide + register for the logged-on user via
scheduled task).

## Pre-release version handling (macOS)

Mozilla's pre-release bundles report only the base version in
`CFBundleShortVersionString` (`153.0` for cask version `153.0b13`),
which would make the generated patch policy flag every up-to-date
install as outdated. Instead, the patch policies for these two channels
compare **`bundle_version`** (`CFBundleVersion` encodes the build date,
e.g. `15326.7.15`), so individual pre-release builds are
distinguishable:

- **Nightly**: derived directly from the build timestamp embedded in the
cask version (`154.0a1,2026-07-17-09-27-13` → `15426.7.17`). No external
calls.
- **Developer Edition**: build date resolved from Mozilla's
[buildhub](https://buildhub.moz.tools) (indexed as product `firefox`,
channel `aurora`). On buildhub failure the ingester logs a warning and
falls back to a base-version comparison (cycle granularity) rather than
failing the run; it self-corrects on the next scheduled ingestion.

The macOS validator gets a matching exception accepting `base + "b"`
versions for the DevEd bundle ID. Both computed values were verified
against the actual DMGs' `Info.plist`.

**Known limitation (Windows):** patch detection for Developer Edition on
Windows is only accurate to the release cycle (153 vs 154), not between
betas (b13 vs b14) — the registry `DisplayVersion` and winget's
`PackageVersion` are both the base version (`151.0`) with no beta
number, and osquery's `programs` table has nothing finer to compare.
Nightly on Windows is day-granular since its MSIX package version
encodes the build date. Documented in #49149.

## Other changes

- Tightened `firefox_uninstall.ps1` (release channel FMA): its
`*Firefox*` match would have uninstalled Developer Edition (or ESR)
first depending on registry enumeration order. Now matches `Mozilla
Firefox*` and excludes ESR; `firefox/windows` output regenerated.
- Channel-specific icons generated from the real app bundles (frontend
components + website PNGs), with map keys covering both catalog and
inventory names.

## Why Firefox Beta is not included

Beta (requested in #49149) was deliberately dropped:
- On Windows, the Beta exe registers the same identity as release
Firefox (`Mozilla Firefox (x64 en-US)`), making it indistinguishable in
inventory. The only distinct package is `Mozilla.Firefox.Beta.MSIX`,
which has no `en-US` winget locale manifest (only `en`), which the
ingester requires.
- On macOS, Beta shares both the bundle ID (`org.mozilla.firefox`) and
the install path (`/Applications/Firefox.app`) with release Firefox, so
installing it silently replaces a release install.

## Notes for reviewers

- Nightly URLs are date-stamped and refresh with the daily FMA
ingestion; within-cycle Nightly darwin builds share a `CFBundleVersion`
date so same-day rebuilds are indistinguishable.
- DevEd's winget package lags Mozilla's releases (winget latest is 151.0
vs current 153.0b13); the FMA tracks winget as usual.
- The `CFBundleVersion` format (`<major><yy>.<M>.<D>`, unpadded) is an
inferred contract of Mozilla's build system, verified against both
shipped DMGs; if it ever changes, patch status fails visibly (reports
unpatched), not silently.

# Checklist for submitter

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

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added deployment support for Mozilla Firefox Developer Edition and
Firefox Nightly on macOS and Windows (including Homebrew/Winget inputs
and platform-specific install/uninstall workflows).
* Added new catalog entries and dedicated UI icons for both Firefox
variants.
* **Bug Fixes**
* Improved macOS “patched” detection and version matching for Developer
Edition and Nightly builds.
* Enhanced Homebrew ingestion by resolving build information (with
fallback when unavailable).
* Refined Windows uninstallation matching to target the correct Firefox
release channels and avoid ESR.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 16:05:27 -05:00
4d07660bdd Update Fleet-maintained apps (#49505)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added updated release metadata for numerous maintained macOS and
Windows applications, including ChatGPT, Claude, Cursor, Camo Studio,
Dayflow, ExtraDock, Granola, Groove OmniDialer, Kiro, MacWhisper,
Postman, Reqable, Spokenly, Stretchly, WhatsApp, Zen, and others.
* Refreshed installer downloads and integrity checks for the latest
builds.

* **Bug Fixes**
* Improved JetBrains Toolbox uninstall to close Toolbox before removing
files.
* Enhanced Stretchly uninstall on Windows with safer cleanup and
PATH/registry fallbacks to better handle unreliable vendor uninstall
behavior.
  * Refined version detection to target the newest installed releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-17 15:09:03 -05:00
efcc0739d1 Update Fleet-maintained apps (#49458)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added an improved Tower uninstall script for winget on Windows that
targets all related MSI products and handles success/reboot scenarios
reliably.
* **Bug Fixes**
* Improved upgrade/patch detection and refreshed version gating for
multiple maintained apps on both macOS and Windows.
* Updated version-specific uninstall cleanup for several JetBrains apps
(and Tower) to remove the correct release-named data.
* **Chores**
* Refreshed installer download URLs and checksums for the latest Arc,
AWS CLI, Brave, Claude, Cursor, Datagrip, Devolutions, Discord, and
others.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-16 22:36:58 -05:00
3e695c79fe Update Fleet-maintained apps (#49392)
Automated ingestion of latest Fleet-maintained app data.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Updated Windows installer metadata (versions, download URLs, and
checksums) for AWS CLI, Fellow, Google Drive, Logi Options+, MongoDB
Compass, Ollama, Signal, TablePlus, TextExpander, and Zed to match the
latest releases.
* **Bug Fixes**
* Improved Google Drive uninstall handling by treating common
success/reboot exit codes as successful outcomes, ensuring non-zero
codes don’t get recorded incorrectly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
2026-07-15 21:25:01 -05:00
Allen Houchins e1ab5b56fd Add Windows FMAs (letter G): 13 apps (#49281)
**Related issue:** N/A — part of the ongoing Windows Fleet-maintained
apps (FMA) parity workstream (letter G).

## What this does

Adds **19** Windows Fleet-maintained apps for the letter-G batch. Each
app has a winget-sourced input, generated output manifest, and (where a
cleanly-licensed ≥256px icon was found) a catalog icon.

**MSI (clean, upgrade-code uninstall):**
- Gadwin PrintScreen, Gadwin PrintScreen Pro, Gadwin ScreenRecorder —
free + the two paid editions are distinct products (separate
ProductCodes/UpgradeCodes), so each matches by exact ARP name to avoid
cross-matching
- GitHub CLI, Go, gsudo, grepWin
- GeoGebra Classic — the machine MSI; the winget manifest's top-level
`Scope: user` forced `installer_scope: user` in the input + custom
machine-MSI install/uninstall scripts
- Google Ads Editor — dual user/machine WiX MSI; custom install forces
`ALLUSERS=1`

**MSI (custom uninstall):**
- GoodSync — `ignore_hash` (non-versioned "latest" URL drifts from the
manifest version/SHA, Chrome/TeamViewer pattern); process-stopping
uninstall for its tray app + sync service

**NSIS / exe (custom install + uninstall):**
- Google Web Designer, Gpg4win (x86-only; versioned ARP name → fuzzy
match), GoAnywhere OpenPGP Studio (install4j `-q`), GoldenDict-ng
(maintained fork; name-only exists query), Graphviz
- Streamlabs Desktop — electron-builder `/S /allusers` +
process-stopping uninstall (versioned ARP name → fuzzy match)

**WiX burn / electron (custom install + uninstall):**
- Garmin BaseCamp, Garmin Express (`ignore_hash` — rolling URL +
self-updating app), Galaxy Modeler (`/S /allusers`)

## Dropped from this batch (recorded in the workstream tracker)
- **Genesys Cloud Background Assistant** — WiX burn bootstrapper with a
hard `VCRedist 2015+ x86` dependency Fleet won't resolve, x86-only,
non-standard burn uninstall.
- **GoldenDict.GoldenDict** — stale original, superseded by the
actively-maintained `xiaoyifang.GoldenDict-ng` fork (shipped instead).
- **GeoGebra GraphingCalculator + Geometry** — user-scope-only exe
installers (no machine option); shipped GeoGebra Classic (MSI) instead.
- **Garden Gnome Package Viewer** — `ggnome.com` download URLs sit
behind a Cloudflare `cf-mitigated: challenge` and return 403 to all
automated requests (even with the Chrome UA), so Fleet's downloader
can't fetch it.

## Notes
- **Gadwin ScreenRecorder** and **Garmin Express** ship without a custom
catalog icon — no cleanly-licensed ≥256px source was found (they fall
back to the generic icon).
- Verification (winget manifest identity, installer type/scope/arch,
ProductCode/UpgradeCode, silent switches, URL stability) was done per
the `new-fma` skill against the winget-pkgs manifests and, where needed,
the real installers.

## Testing
- [ ] FMA CI validator (install → detect → uninstall) on the
SYSTEM-context Windows runner — pending.
- Generated outputs verified locally: all 19 produce valid manifests;
MSI apps carry the correct UpgradeCode-based uninstall; exists/patched
queries reviewed for name + publisher correctness.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added maintained Windows software catalog entries for 13 applications
(Gadwin PrintScreen/Pro/ScreenRecorder, Galaxy Modeler, Garmin BaseCamp,
GeoGebra Classic, Go, GoAnywhere OpenPGP Studio, GoldenDict-ng, Google
Ads Editor, Google Web Designer, Graphviz, grepWin).
* Enabled silent install/upgrade detection and automated uninstall
behavior for the newly supported apps.
* Added app icons and expanded software-name keyword matching for
improved identification in the catalog.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 21:33:12 -05:00
Allen Houchins 1a81e3551f Add Mozilla VPN as a macOS and Windows FMA (#49284)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49149

Adds **Mozilla VPN** as a Fleet-maintained app for both macOS (Homebrew
cask `mozilla-vpn`) and Windows (winget `Mozilla.VPN`), version 2.38.0.

Identity fields verified against the real installers (not catalog
metadata):

| | macOS | Windows |
|---|---|---|
| `unique_identifier` | `org.mozilla.macos.FirefoxVPN`
(CFBundleIdentifier from pkg PackageInfo) | `Mozilla VPN` (MSI
`ProductName`) |
| Publisher | — | `Mozilla Corporation` (MSI `Manufacturer` = winget
locale, no override needed) |
| Format/type | `pkg` | `msi` (winget `wix`, machine scope,
`ALLUSERS=1`) |

- Install/uninstall scripts auto-generated (machine-scope MSI + cask
artifacts/zap) — no custom scripts.
- Generated SHAs match the manifests (macOS `2803d4b4…`, Windows
`11a270b3…`).
- No bootstrapper (`ARPSYSTEMCOMPONENT` absent); pinned installer URLs;
no risk flags.
- On Windows, osquery reports `programs.version` as `2.38.0.0` vs the
FMA's `2.38.0`; `version_compare` treats a fresh install as ≥ target, so
the patch policy reports patched correctly.
- New app icon generated (`MozillaVpn.tsx`, website PNG, `index.ts` map
key `"mozilla vpn"` shared by both platforms).

# Checklist for submitter

- [ ] QA'd all new/changed functionality manually
2026-07-14 13:58:33 -05:00
Allen Houchins 7ba5d4cfcc Add Windows FMAs (letter F): 5 apps (#49218)
**Related issue:** N/A — part of the Windows Fleet-maintained apps
catalog expansion (letter F batch; follows #48872, #48881, #48950,
#48969, #49086, #49186).

Adds eight new Windows Fleet-maintained apps:

| App | winget package | Installer | Notes |
|-----|----------------|-----------|-------|
| Foxit PDF Editor | `Foxit.PhantomPDF` | WiX bootstrapper EXE, machine,
x64 | Covers both "Foxit PDF Editor" and "…Pro" from the inventory (one
package). Runs an updater service → process-stopping uninstall. ARP key
lives in the WOW6432Node hive. |
| Foxit PDF Reader | `Foxit.FoxitReader` | WiX bootstrapper EXE,
machine, x64 | Distinct DisplayName from the Editor (verified via
msiinfo). Updater service → process-stopping uninstall. |
| FreeCAD | `FreeCAD.FreeCAD` | NSIS (MultiUser), machine, x64 |
`/AllUsers /S`; versioned ARP name ("FreeCAD 1.1.1") → `FreeCAD%` fuzzy.
|
| FastPictureViewer Professional |
`AxelRietschin.FastPictureViewer.Professional` | MSI, machine, x64 |
Versioned ARP name → fuzzy. Unversioned URL → `ignore_hash`. Declares
VCRedist deps (near-ubiquitous; noted). |
| FastStone Capture | `FastStone.Capture` | NSIS, machine, x86 | `/S`;
versioned ARP name → fuzzy. Paid trialware, but silent
install/detect/uninstall are clean. |
| FastStone Image Viewer | `FastStone.Viewer` | NSIS, machine, x86 |
`/S`; versioned ARP name → fuzzy. |
| FlexWhere for Desktop | `Dutchview.Flexwhere` | MSI, machine, x64 |
Auto-start tray app → process-stopping uninstall (stop process, then
msiexec /x via UpgradeCode). |
| Fortify | `PeculiarVentures.Fortify` | WiX MSI, machine, x64 (en-US) |
Smart-card/cert bridge; auto-start tray → process-stopping uninstall.
Per-arch+locale ProductCode, so `installer_locale: en-US`. |

Considered but **not** added (recorded in the workstream tracker):
- **FactSet Workstation** (`FactSet.FactSetWorkstation`): MSI defaults
to per-user (ALLUSERS=2 + MSIINSTALLPERUSER=1) with no machine-scope
override, AND a `SpawnFDSWorkstation` custom action launches the app at
install (headless-hang risk in a SYSTEM session). Niche licensed
terminal.
- **Filius** (`StefanFreischlad.Filius`): winget manifest is de-DE only
(no en-US locale); the ingester hard-codes the en-US locale fetch (same
limitation that deferred Araxis Merge).
- **FlashFXP** (`OpenSight.FlashFXP`): abandoned (frozen at 2017), the
vendor site returns HTTP 500, only a 16×16 icon is available, and its
InstallAware uninstall needs a fragile cached-setup `/s` injection.
- **Front** (`FrontApp.Front`): per-user-only electron-builder installer
(`Front-user-*.exe` → `%LocalAppData%`, HKCU); no machine/all-users
artifact in winget.
- **Autodesk Fusion** (`Autodesk.Fusion`): the winget "installer" is
`Fusion Client Downloader.exe`, a per-user streaming/web bootstrapper
that downloads at runtime, hangs headless, and needs interactive
Autodesk sign-in.

Identities verified per app (msiinfo Property tables; NSIS header
decompilation; winget AppsAndFeaturesEntries; uninstall-database
corroboration). Apps that run a service or auto-start tray (both Foxit
products, FlexWhere, Fortify) get process-stopping uninstalls up front
to avoid the MSI-rollback failure class. SHAs verified against manifests
for pinned URLs; `ignore_hash` only for FastPictureViewer's
actively-maintained latest-pointer URL. Icons via
`tools/software/icons/generate-icons.sh` (all ≥256px except
FastPictureViewer/Fortify at 256/180).

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [ ] QA'd all new/changed functionality manually (relying on the FMA CI
validator for Windows install/uninstall validation)
2026-07-13 15:07:06 -05:00
Allen Houchins 7ac8c659e0 Add Windows FMAs (letter E): 13 apps (#49186)
**Related issue:** N/A — part of the Windows Fleet-maintained apps
catalog expansion (letter E batch; follows #48872, #48881, #48950,
#48969, #49086).

Adds eleven new Windows Fleet-maintained apps:

| App | winget package | Installer | Notes |
|-----|----------------|-----------|-------|
| Eclipse Temurin JDK 8 | `EclipseAdoptium.Temurin.8.JDK` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JDK 11 | `EclipseAdoptium.Temurin.11.JDK` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JDK 17 | `EclipseAdoptium.Temurin.17.JDK` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JDK 21 | `EclipseAdoptium.Temurin.21.JDK` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JRE 8 | `EclipseAdoptium.Temurin.8.JRE` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JRE 11 | `EclipseAdoptium.Temurin.11.JRE` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JRE 17 | `EclipseAdoptium.Temurin.17.JRE` | MSI (WiX),
machine, x64 | Per-major pin. |
| Eclipse Temurin JRE 21 | `EclipseAdoptium.Temurin.21.JRE` | MSI (WiX),
machine, x64 | Per-major pin. |
| exacqVision Client | `ExacqTechnologies.exacqVisionClient` | MSI,
machine, x64 | Clean MSI; ARP name `exacqVision Client (x64)`. |
| Egnyte WebEdit | `Egnyte.EgnyteWebEdit` | MSI, machine, x86 | Distinct
product from the existing Egnyte Desktop FMA (separate
ProductCode/UpgradeCode/ARP name). |
| Elevate UC | `Serverdata.ElevateUC` | MSI, machine, x64 | Intermedia
UC client. Unversioned latest-pointer URL with ~monthly cadence →
`ignore_hash`. |

**Eclipse Temurin (8 apps).** All are clean machine-scope WiX MSIs from
Eclipse Adoptium. The ARP DisplayName embeds the full patch version —
`Eclipse Temurin JDK with Hotspot 17.0.19+10 (x64)` — and JDK/JRE of the
same major share a version prefix, so each major is pinned with an
`exists_query` that combines the JDK-vs-JRE name prefix, the publisher,
and a major version filter, e.g.:
```
SELECT 1 FROM programs WHERE name LIKE 'Eclipse Temurin JDK%' AND publisher = 'Eclipse Adoptium' AND version LIKE '17.%';
```
The `JDK`/`JRE` token in the name prefix keeps a JDK install from
matching the JRE FMA and vice-versa; the `version LIKE '<major>.%'`
keeps each major distinct. This mirrors the existing Amazon Corretto
per-major FMAs. Identities (DisplayName, publisher `Eclipse Adoptium`,
4-part ProductVersion) were verified via `msiinfo` on the real x64 MSIs.

Considered but **not** added (recorded in the workstream tracker):
- **ESET Endpoint Antivirus** (`ESET.EndpointAntivirus`) and **ESET
Endpoint Security** (`ESET.EndpointSecurity`): the install succeeds
headless without a license, but uninstall is Self-Defense (HIPS)
protected — it requires a reboot to complete and is widely documented to
fail unattended (needing the ESET Uninstaller Tool in Safe Mode), so a
reliable silent SYSTEM-context removal can't be guaranteed. They're also
managed enterprise agents meant for central ESET PROTECT deployment
(standalone installs land unactivated and disable Windows Defender).

Still to verify (not in this PR): EndNote, Enpass, Evernote, and
UltraISO — their verification pass was interrupted and will be handled
separately.

Identities verified via `msiinfo` Property tables. SHAs verified against
manifests for pinned URLs; `ignore_hash` used only for Elevate UC's
actively-maintained latest-pointer URL. Icons via
`tools/software/icons/generate-icons.sh`.

# Checklist for submitter

- [x] 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [ ] QA'd all new/changed functionality manually (relying on the FMA CI
validator for Windows install/uninstall validation)
2026-07-13 09:59:17 -05:00
Allen Houchins 99cd91a32c Fuzzy-match Zoom Workplace program name in Windows FMA install and patch query (#49188)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** N/A

## Summary

The Windows Zoom Fleet-maintained app's `exists`/`patched` queries used
an exact match on `programs.name = 'Zoom Workplace (X64)'`. Real-world
Zoom installs frequently register under a different name:

- Winget's own package name for `Zoom.Zoom` is `Zoom Workplace` (no arch
suffix).
- Zoom ships a separate, per-user, self-updating installer
(`Zoom.Zoom.EXE`, ProductCode `ZoomUMX`) that many end users get via
Zoom's in-app auto-updater, which registers differently than the
MSI-based entry Fleet's FMA targets (see
[microsoft/winget-pkgs#151467](https://github.com/microsoft/winget-pkgs/issues/151467)).
- ARM64 builds exist and would register as e.g. `Zoom Workplace
(ARM64)`.

Since the `patched` query is generated directly from the `exists` query,
any host with Zoom installed under one of these other names was
invisible to the patch policy — it would never show as needing (or
having received) an update.

- `ee/maintained-apps/inputs/winget/zoom.json`: added
`"fuzzy_match_name": "Zoom Workplace%"` (following the existing
precedent used by ~97 other Windows FMAs, e.g. `cinc.json`'s custom
fuzzy pattern).
- `ee/maintained-apps/outputs/zoom/windows.json`: regenerated via `go
run cmd/maintained-apps/main.go --slug="zoom/windows" --debug`. Only the
`exists`/`patched` query strings changed (now `name LIKE 'Zoom
Workplace%'` instead of `name = 'Zoom Workplace (X64)'`); version,
install/uninstall scripts, and refs are untouched.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`

## Testing

- [x] Verified the regenerated output only changes the exists/patched
query strings (`git diff`)
- [ ] QA'd all new/changed functionality manually (needs a live Windows
host with Zoom installed under a non-`(X64)` name to fully confirm)
2026-07-13 09:24:53 -05:00