3640 Commits
Author SHA1 Message Date
Dante Catalfamo 4e6591e09d Reconcile stuck Android MDM commands via AMAPI operations.get (#50177)
**Related issue:** Resolves #46145
2026-08-07 15:21:58 -04:00
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
dependabot[bot] d9a2445832 Bump socket.io-parser from 4.2.6 to 4.2.7 in /website (#50504)
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from
4.2.6 to 4.2.7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/socketio/socket.io/releases">socket.io-parser's
releases</a>.</em></p>
<blockquote>
<h2>socket.io-parser@4.2.7</h2>
<h3>Bug Fixes</h3>
<ul>
<li>honor toJSON() when deconstructing a binary packet (<a
href="https://redirect.github.com/socketio/socket.io/issues/5518">#5518</a>)
(<a
href="https://github.com/socketio/socket.io/commit/57f111439513809c633f2554be2f3104e4ad432c">57f1114</a>)</li>
<li>reject binary packets with zero attachments (<a
href="https://github.com/socketio/socket.io/commit/7c6ef571a00656718e9e05e3b948fd1758b2a7b4">7c6ef57</a>)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/spokodev"><code>@​spokodev</code></a>
made their first contribution in <a
href="https://redirect.github.com/socketio/socket.io/pull/5518">socketio/socket.io#5518</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/socketio/socket.io/commit/4054894738817f5a2125e6e6b18e79d92c75ab33"><code>4054894</code></a>
chore(release): socket.io-parser@4.2.7</li>
<li><a
href="https://github.com/socketio/socket.io/commit/7c6ef571a00656718e9e05e3b948fd1758b2a7b4"><code>7c6ef57</code></a>
fix(parser): reject binary packets with zero attachments</li>
<li><a
href="https://github.com/socketio/socket.io/commit/57f111439513809c633f2554be2f3104e4ad432c"><code>57f1114</code></a>
fix(parser): honor toJSON() when deconstructing a binary packet (<a
href="https://redirect.github.com/socketio/socket.io/issues/5518">#5518</a>)</li>
<li><a
href="https://github.com/socketio/socket.io/commit/8d2e4f7b8671ed2c4fa1fca9ec6f68e5cf82266e"><code>8d2e4f7</code></a>
docs(security): add CVE-2026-59724 and CVE-2026-59725</li>
<li><a
href="https://github.com/socketio/socket.io/commit/d2d753fed4435015c2d83fe62e676b44e07fa3f7"><code>d2d753f</code></a>
refactor(sio): align client file matching in Node.js HTTP server</li>
<li><a
href="https://github.com/socketio/socket.io/commit/dfb5ab3b6a2f00d1ecb226939022ea56e5701124"><code>dfb5ab3</code></a>
fix(sio): prevent uWebSockets.js from serving missing client files</li>
<li><a
href="https://github.com/socketio/socket.io/commit/dcbd96154352dd84c189b41e5c0ec0df4b7fe6a5"><code>dcbd961</code></a>
perf(eio): optimize polling request body buffering</li>
<li><a
href="https://github.com/socketio/socket.io/commit/6bb2e7f5fb7c548f0776328b4a19719723e27efd"><code>6bb2e7f</code></a>
refactor(sio): internalize base64id dependency</li>
<li><a
href="https://github.com/socketio/socket.io/commit/a80711a8c3b11a3e75a84de567ac4d84820f4714"><code>a80711a</code></a>
refactor(eio): internalize base64id dependency</li>
<li><a
href="https://github.com/socketio/socket.io/commit/8bead0f4c6fba027996bb9eab02f05120f35513d"><code>8bead0f</code></a>
chore: upgrade to TypeScript 6</li>
<li>Additional commits viewable in <a
href="https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.6...socket.io-parser@4.2.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=socket.io-parser&package-manager=npm_and_yarn&previous-version=4.2.6&new-version=4.2.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/fleetdm/fleet/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 11:59:28 -05:00
Eric f145c778b8 Website: log number of android enterprise requests in the past minute (#50780)
Related to: https://github.com/fleetdm/fleet/issues/49212

Changes:
- Updated the custom hook to create `sails.androidProxyApiRequestCount`,
and to log and reset the value every minute
- Updated android proxy endpoints to increment
`sails.androidProxyApiRequestCount` every time a request to the Android
management API is sent

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

* **Monitoring**
* Added comprehensive tracking for Android Management API requests
across enrollment, enterprise, device, application, policy, and command
operations.
* Added periodic request-count logging and automatic resets when Android
Enterprise credentials are configured.
* Improved reporting alignment with minute-based API limits while
keeping logs quiet during periods without requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-07 11:58:35 -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
b5940c8eca Bump brace-expansion in /website (#50453)
Bumps and
[brace-expansion](https://github.com/juliangruber/brace-expansion).
These dependencies needed to be updated together.
Updates `brace-expansion` from 2.1.2 to 2.1.4
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/b25213dff0446d622f97d736420b9830ee1abc32"><code>b25213d</code></a>
2.1.4</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/1e30c930238d7162802d88a94189182def178dac"><code>1e30c93</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/878df3989e816dfb28cbe0d64de0b88738ff0ed6"><code>878df39</code></a>
2.1.3</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/c8bd93cfff4e45cb295557d2be17e1d1d4e52a11"><code>c8bd93c</code></a>
npm ignore .claude</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/d13ff455a58b0d56704f0111e3c2a0b16ceb06eb"><code>d13ff45</code></a>
fix: backport GHSA-mh99-v99m-4gvg (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/130">#130</a>)</li>
<li>See full diff in <a
href="https://github.com/juliangruber/brace-expansion/compare/v2.1.2...v2.1.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `brace-expansion` from 1.1.16 to 1.1.18
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/b25213dff0446d622f97d736420b9830ee1abc32"><code>b25213d</code></a>
2.1.4</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/1e30c930238d7162802d88a94189182def178dac"><code>1e30c93</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/878df3989e816dfb28cbe0d64de0b88738ff0ed6"><code>878df39</code></a>
2.1.3</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/c8bd93cfff4e45cb295557d2be17e1d1d4e52a11"><code>c8bd93c</code></a>
npm ignore .claude</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/d13ff455a58b0d56704f0111e3c2a0b16ceb06eb"><code>d13ff45</code></a>
fix: backport GHSA-mh99-v99m-4gvg (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/130">#130</a>)</li>
<li>See full diff in <a
href="https://github.com/juliangruber/brace-expansion/compare/v2.1.2...v2.1.4">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/fleetdm/fleet/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Luke Heath <luke@fleetdm.com>
Co-authored-by: Eric <eashaw@sailsjs.com>
2026-08-06 22:54:40 -05:00
melpikeandEric e6f1abe5a2 [Route] Add route for DDM activations learn more page (#50465)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48222

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

* **Documentation**
* Added a redirect from the DDM activations information page to the
relevant section of the Custom OS Settings article.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Eric <eashaw@sailsjs.com>
2026-08-06 13:52:13 -06:00
LeAnn 7e22e9f811 Add route for manual sync guide (#50693)
**Related issue:** #50001


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

## Summary by CodeRabbit

* **New Features**
* Added a redirect from the Android manual sync learn-more page to the
updated setup guide.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-06 09:34:48 -07:00
melpike 392f62dc08 [Route] Add route for Linux disk encryption guide (#50191)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48654 

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

* **New Features**
* Added a redirect from the Linux disk encryption information page to
the relevant enforcement guide section.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-06 07:23:02 -06:00
Noah Talerman 39d57caa09 Release article: Fleet 4.90.0 (#50240) 2026-08-05 20:42:47 -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
Eric 1563aeb70c Website: improve speed of deliver-talk-to-us-form-submission action (#50593)
Changes:
- Updated the model used in the prompt helper calls in the
deliver-talk-to-us-form-submission action and the get-enriched helper to
improve the speed of routing users booking a demo.

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

## Summary by CodeRabbit

* **Improvements**
* Updated location and address enrichment to use an updated language
model, improving the processing of submitted information while
preserving existing form behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-05 11:42:13 -05:00
Eric 85692f8238 Website: set historical event source (#50546)
Changes:
- Updated the website's createHistoricalEvent helper to accept an
eventSource input that is used to set the historical event source on
created records.
- Updated places where we create historical events to set a historical
event source
- Updated the accepted contact sources values in the receive-from-clay
webhook
- Updated the deliver-gitops-workshop-request action to log a warning
when a campaign member record cannot be created

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

- **Improvements**
- Improved activity tracking for newsletter subscriptions, signups,
contact forms, workshop requests, webinars, gated content, and page
views.
- Added clearer source details to records for more accurate attribution.
- Expanded support for website, webinar, event, LinkedIn, prospecting,
and GitHub activity sources.
- **Bug Fixes**
- Workshop requests now continue successfully if campaign updates
encounter an error.
- Corrected warning messages and preserved relevant submission details
for troubleshooting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-04 18:26:14 -05:00
melpike 8a11bd58db [Route] Update routes.js (#50472)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48894 

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

## Summary by CodeRabbit

* **New Features**
* Added a redirect from `/learn-more-about/removal-behavior` to the
relevant section of the custom OS settings guide.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-04 07:11:56 -06:00
Allen Houchins b668734d5c Remove Fig FMA (cask removed from Homebrew upstream) (#50483)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** NA

Removes the **Fig** Fleet-maintained app. Its Homebrew cask no longer
exists.

Homebrew deleted the `fig` cask in
[`a36fac3b75`](https://github.com/Homebrew/homebrew-cask/commit/a36fac3b75f633a9c787c4c73fc46606939947a3)
on **2026-08-04**, the end of a long deprecation:

| Date | Upstream change |
|---|---|
| 2024-08-03 | `fig: deprecate` |
| 2025-08-02 | `fig: disable` |
| 2026-08-04 | `fig: remove cask` |

`https://formulae.brew.sh/api/cask/fig.json` now returns **404**, so the
nightly ingester panics and no maintained apps are generated at all:

```
{"time":"2026-08-04T02:38:58.15272663Z","level":"INFO","msg":"ingesting homebrew app","name":"fig"}
panic: ingesting homebrew app: app not found in brew API
```

Note that `"frozen": true` does **not** fix this — that flag only gates
the output write, and the ingester still fetches the cask first and
panics on the 404. Removing the input is the fix.

There is no successor cask to migrate to. Fig was acquired by AWS and
folded into Amazon Q Developer CLI, which is not distributed via
Homebrew (`amazon-q`, `amazon-q-developer-cli`, `q-cli`, and
`codewhisperer` all 404), and `fig.io` itself now returns 503.

### Changes

- Deleted `ee/maintained-apps/inputs/homebrew/fig.json`
- Deleted `ee/maintained-apps/outputs/fig/darwin.json`
- Removed the `fig/darwin` entry from
`ee/maintained-apps/outputs/apps.json`
- Deleted the `Fig` icon component and its `index.ts` import/map entry
- Deleted `website/assets/images/app-icon-fig-60x60@2x.png`

The output file is deleted rather than orphaned so the PR validator's
changed-app detector doesn't keep validating a removed app.

# 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

Verified `apps.json` still parses and the `fig/darwin` slug is gone
(1390 apps remain, `figma` untouched), `tsc --noEmit` is clean after
removing the `Fig` icon import, and no references to `fig`,
`com.mschrage.fig`, `fig/darwin`, or `repo.fig.io` remain anywhere in
the repo.

> [!NOTE]
> Existing hosts with Fig installed will no longer see it as a
Fleet-maintained app. The app is end-of-life upstream, so there is no
version for Fleet to track or patch to.
2026-08-04 07:19:45 -05:00
Allen HouchinsandEric 19af21dd1a Upgrade query-generator SQL step to Claude Sonnet 5 (#49187)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** N/A

## What this does

The `/query-generator` page's osquery-SQL-generation step
([get-llm-generated-sql.js](website/api/controllers/query-generator/get-llm-generated-sql.js))
was on `claude-sonnet-4-6`, which is now one generation behind. This PR:

- Bumps that call to `claude-sonnet-5`. The schema-filtration step stays
on `claude-haiku-4-5`, which is already the latest Haiku release, so no
change needed there.
- Adds `effort` support to the shared [`ai.prompt`
helper](website/api/helpers/ai/prompt.js), forwarded as
`output_config.effort` on Anthropic requests, and sets it to `"low"` for
the SQL-generation call. Effort controls how much the model deliberates
(and how many tokens/how much latency that costs). `"low"` was chosen
because the Haiku pre-filtering step already narrows the osquery schema
down to relevant tables, so the Sonnet step isn't starting from scratch
and doesn't need to spend much effort re-deriving that context.
- Bumps `max_tokens` in the Anthropic branch of the helper from 4096 to
8192. Claude Sonnet 5 turns on adaptive thinking by default when the
`thinking` param is omitted (which this helper does), and `max_tokens`
is a hard cap on *total* output including thinking tokens — at 4096
there was a real risk of thinking tokens eating into the budget and
truncating the JSON response the SQL step needs to return.
- **Fixes a pre-existing bug found while making the above changes:** the
`sqlReport` call passed the system prompt as a bare object-shorthand key
named `systemPromptForQueryGeneration`, but the `ai.prompt` helper's
declared input is `systemPrompt`. Sails silently drops unrecognized keys
passed to `.with(...)`, so the "Return ONLY a raw JSON object..." system
prompt was never actually reaching the model for this call. This has
been broken since the query generator was switched to Anthropic
(`f7c20c4731`); the sibling `filteredTables` call above it was
unaffected since it passes `systemPrompt` positionally. Now fixed to
`systemPrompt: systemPromptForQueryGeneration`.

## Why

Claude Sonnet 5 follows structured/constrained instructions (don't alias
tables, use `LIKE` with wildcards, only reference documented columns,
etc.) more literally than 4.6, which should make the generated SQL more
reliable. It's priced the same or cheaper than 4.6 during the current
introductory period.

## Trade-offs called out for review

- Thinking being on by default adds some latency versus the old
(thinking-off) behavior on 4.6. This call is not currently streamed
(`sails.helpers.http.post`, single blocking call over a socket), so any
added thinking time is invisible wait time for the user rather than a
visible "thinking" indicator. `effort: "low"` should keep this modest,
but worth confirming with a manual QA pass on a few representative
questions before merging.
- Only the SQL-generation call was migrated. The schema-filtration call
also runs on an Anthropic model, but Haiku 4.5 doesn't support
`output_config.effort` (added `effort` is a no-op if passed to it), so
it was left as-is.

# 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.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

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

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


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

* **Improvements**
  * Improved AI-generated SQL responses with an updated language model.
  * Added adaptive effort controls for supported AI requests.
* Increased response capacity to support more detailed generated
results.
* Improved handling of AI responses to provide more reliable results
when content includes different response formats.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Eric <eashaw@sailsjs.com>
2026-08-03 21:39:40 -05:00
Allen Houchins 033cb8843c Fix image captions breaking onto multiple lines when they contain links (#50441)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** NA

Image captions on articles broke onto multiple lines whenever the
caption contained an inline link. Reported on [Linux crossed 10% in
North America, and your inventory might have missed
it](https://fleetdm.com/articles/linux-crossed-10-percent-and-your-inventory-missed-it),
where the source line under the Cloudflare Radar chart rendered as:

```
Source:
Cloudflare Radar
, captured August 3, 2026.
```

## Cause

Image captions (`img + em`) were styled as a column flex container:

```less
display: flex;
align-items: center;
flex-direction: column;
```

Every child of a flex container becomes its own flex item on its own
line — including bare text nodes, which get wrapped in anonymous flex
items. That caption has three children (`"Source: "`, the `<a>`, and `",
captured August 3, 2026."`), so it stacked into three rows.

Every other image caption in `articles/` is a single plain-text run,
which is why this hasn't surfaced before. This is the first caption on
the site with an inline link.

## Fix

```less
img + em { // Image captions
  position: relative;
  top: -12px;
  display: block;
  text-align: center;
  margin-bottom: 16px;
}
```

`display: block` + `text-align: center` preserves the existing centered
appearance for plain-text captions (visually identical) while letting
inline content flow normally.

## Notes for the reviewer

- **Six files, one rule.** The identical caption rule was copy-pasted
into `basic-article.less`, `basic-webinar.less`,
`basic-whitepaper.less`, `case-study.less`, `legal/privacy.less`, and
`legal/terms.less`. Only `basic-article.less` is needed to fix the
reported page; the other five carry the same defect, so all six are
updated rather than leaving the trap for the next caption with a link.
- **No markdown change.** The article source (`*Source: [Cloudflare
Radar](...), captured August 3, 2026.*`) was already correct. This is
purely a stylesheet fix.
- **Mobile overrides untouched.** The second `img + em` block in each
file (inside a media query) only adjusts `top` and `margin-bottom`, so
it needed no change.
- **Existing captions are unaffected.** All current captions are single
text runs; block + centered text renders them the same as column flex +
`align-items: center` did.

# Checklist for submitter

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

Verified on the live article by injecting the new rule into the rendered
page: the caption went from 72px tall (three stacked lines) to 24px (one
centered line), rendering as `Source: Cloudflare Radar, captured August
3, 2026.`

I was not able to run `npm run lint` locally — `website/node_modules` is
not installed in my working copy. The change follows `.lesshintrc` (one
space after `:`, no `!important`), but CI's lint run is the authority
here.


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

## Summary by CodeRabbit

* **Style**
  * Improved image caption layout across articles and legal content.
* Captions now display consistently as centered block text for better
readability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 21:37:45 -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
Eric f736356f0e Website: update contact form button (#50477)
Changes:
- updated the submit button on the "Talk to us" form ("Talk to an
engineer" » "Pick a time")
- Updated the GitHub webhook to add ~ga4-annotation labels to pull
requests that change the contact page

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

* **New Features**
* Updated the contact form button label to “Pick a time” for clearer
scheduling intent.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 17:52:25 -05:00
Eric 74c2b79f05 Website: add support for new usage statistics (#50466)
Changes:
- Updated the website to support two new usage statistics sent by Fleet
servers: `numHostsFleetMDMEnrolledWindows` and
`numHostsFleetMDMEnrolledMacOS`

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

## Summary by CodeRabbit

* **New Features**
* Usage analytics now track Fleet MDM-enrolled host counts separately
for macOS and Windows.
* Historical usage records include these platform-specific enrollment
totals.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 15:15:02 -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
Eric 05ec868641 Website: Update logos on contact page (#50368)
Changes:
- Replaced the logo carousel on the contact page with a grid of six
logos.

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

## Summary by CodeRabbit

* **Style**
* Updated the contact page to display partner logos in responsive two-
and three-column grids.
* Improved logo sizing, alignment, spacing, and borders across desktop
and mobile screen sizes.
* Replaced the previous carousel presentation with a static, more
consistent layout.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 10:59:33 -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
Allen Houchins 7abc0f6e7f Add article: Linux crossed 10% in North America, and your inventory might have missed it (#50412)
**Related issue:** NA

New thought-leadership article pegged to Statcounter's July 2026 data
showing Linux desktop share at 10.65% in North America.

The angle is deliberately honest rather than triumphant: most of the
month-over-month jump came from Statcounter reclassifying an "Unknown"
traffic bucket, not from a mass migration. That measurement problem is
the bridge into Fleet's argument, since the same blind spot that hid
Linux from web analytics hides Linux hosts from asset inventories that
discover devices through Apple and Windows enrollment channels.

Includes a Cloudflare Radar chart as a second, independent data source.
Cloudflare puts North American Linux desktop requests at 6.3% over the
trailing year, well below Statcounter's figure, but both show a step
change in the same weeks. The disagreement between the two sources
reinforces the article's point that Linux is hard to count from the
outside.

Intended for use in ads and other traffic-driving placements.

Notes for reviewers:

- Capability claims (LUKS2 enforcement on Ubuntu/Kubuntu/Fedora with
escrowed recovery keys, `.deb`/`.rpm`/`.tar.gz` and script-only
packages, vulnerability detection including CISA KEV, remote script
execution, remote lock and wipe, self-service, supported distro list)
are grounded against `website/views/pages/linux-management.ejs`. Please
confirm they are still accurate.
- The article states that encryption enforcement covers a narrower set
of distributions than the overall support list, which is intentional.
- Statcounter figures are cited via Linuxiac; a Sources section
attributes both Statcounter and Cloudflare Radar, and the chart carries
inline attribution and a capture date.
- `publishedOn` is set to `2026-08-02`. Bump if this merges later.
- Byline is set to Allen Houchins.

# Checklist for submitter

- [ ] Content reviewed for Fleet voice and style (`content-style`) and
article format (`fleet-article-formatting`)
- [ ] Capability claims verified against the Linux management page and
docs
- [ ] Source attribution confirmed for Statcounter, Linuxiac, and
Cloudflare Radar
- [ ] Article renders correctly on the website, including the chart
image
2026-08-03 00:49:10 -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
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
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
kitzyandClaude 2833401d12 Add Dante Controller as a macOS FMA (#50378)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #50377

# What this does

Adds **Dante Controller** as a macOS Fleet-maintained app, from Homebrew
cask
[`dante-controller`](https://formulae.brew.sh/cask/dante-controller)
(4.18.1.1). The cask ships a DMG containing `DanteController.pkg`, so
`installer_format: "dmg"` with the standard mount-and-`installer -pkg`
install script — the same shape as AdGuard, Adobe Acrobat Pro, and other
existing DMG+pkg FMAs. Uninstall is fully generated from the cask's
directives (2 `launchctl` services, 8 `pkgutil` receipt IDs, 3 zap trash
paths) — no custom scripts.

Windows is out of scope: no `Audinate.DanteController` winget package
exists. See #50377 for the full feasibility analysis, including why
Dante Virtual Soundcard was excluded on both platforms.

## Notes

- **Identity verified against the real installer**, not cask metadata.
Extracted `DanteController.pkg` from the DMG and read the app bundle's
`Info.plist`: `CFBundleIdentifier` =
`com.audinate.dante.DanteController`, confirming the input's
`unique_identifier`. This needed checking because the cask's preferences
path uses a *different* domain (`com.audinate.dante.controller`).
- `CFBundleShortVersionString` and `CFBundleVersion` are both
`4.18.1.1`, matching the cask version, so the exists/patched queries
reconcile with osquery's `apps` table.
- Installer SHA confirmed against a local download of the DMG
(`4515cd12…38ff`) — matches both the cask and the generated output.
- **Arch split.** The cask ships separate arm64/x64 DMGs; the brew API's
top-level URL (which the ingester pins) is the arm64 build. This matches
the behavior of the ~173 existing darwin FMAs with arch-specific URLs.
- **`auto_updates true`.** Dante Updater self-patches, so hosts may
drift ahead of the FMA-pinned version.
- Ships a new catalog icon, extracted from the app bundle's own `.icns`
— not sourced from the web.

# 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] FMA CI validator (install → detect → uninstall) **passes** on the
macOS runner — [run
30682752130](https://github.com/fleetdm/fleet/actions/runs/30682752130/job/91322957939)
(`Found app: 'Dante Controller' at /Applications/Dante Controller.app,
Version: 4.18.1.1` → `All 1 apps were successfully validated.`)
- [x] Generated output verified locally: installer SHA matches the cask,
exists/patched queries checked against the app bundle's `Info.plist`,
`apps.json` is valid JSON with a description filled in.
- [x] QA'd all new/changed functionality manually

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-02 20:25:36 -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
Dale Ribeiro bf26771ff1 Delete articles/seamless-mdm-migration.md (#49029) 2026-07-31 18:35:54 -04:00
Eric ba7168f54b Website: update contact source (#50356)
Changes:
- Updated the contact source used for contacts created by the
deliver-webinar-access-request action (Webinar » Website - Gated video)

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

## Summary by CodeRabbit

- **Bug Fixes**
- Webinar access requests are now categorized as “Website - Gated video”
in Salesforce.
- Updated contact and account records use the new source label
consistently.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 17:20:40 -05:00
Mike McNeil 46aed2da6b Website: Update software-management.ejs so that we mention the 1000+ apps in the catalog (#50345)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated the software management page to link to Fleet’s software
catalog.
  * Clarified that the catalog includes 1,000+ apps.
* Noted automatic updates across macOS, Windows, and additional
platforms.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 16:16:58 -05:00
Mike ThomasandClaude Sonnet 5 45fa8be602 Website: add press logos to /ai (#50281)
Adds press logos below "The road to AI in IT" report card.

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

# 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 a press coverage section to the AI in IT page.
* Displays logos for Apple World Today, Cybersecurity Insiders, and IT
Brief, linking to related articles.
  * Added responsive layouts for desktop, tablet, and mobile screens.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 11:14:30 -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
Mike Thomas 8af6eb8ad9 fix: correct tooling stat to match survey data (#50284)
**Related issue:** Resolves #

# Checklist for submitter

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

Updated stat to lead with 87% and fixed wording to match actual data
from the report.

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

## Summary by CodeRabbit

* **Content Updates**
* Updated the AI in IT report statistic to state that 87% of
organizations manage devices with multiple tools.
  * Clarified that 13% use a single platform.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 18:03:18 +09:00
Eric ccfcc65fc5 Website: add /capex-savings page (#50272)
Changes:
- Added  a new landing page: /capex-savings

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

* **New Features**
  * Added a CapEx savings page available at `/capex-savings`.
* Added guidance on device refresh cycles, hardware performance, and
recommended replacement timing.
* Added a savings calculator for device cost, refresh cycle, and fleet
size, with estimated annual savings and assumptions.
* Added responsive layouts, imagery, calls to action, and
mobile-friendly calculator controls.
* Added supporting content on refresh recommendations, Fleet hardware
performance, and device lifecycle planning.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 14:10:50 +09:00
Eric db323dcc0a Website: Swap quote on contact page (#50259)
Changes:
- updated the quote shown on the /contact page

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

* **Content Updates**
* Updated the contact page testimonial with refreshed customer feedback
and revised attribution.
* Updated the testimonial’s company logo, as well as the author
portrait, name, and job title.

* **Style**
* Adjusted the testimonial profile image styling to render as a fully
circular image.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-30 15:00:30 -05:00
Eric 58642183c6 Website: update contact source for workshop requests (#50072)
Changes:
- Updated the contact source used for contacts created from workshop
requests

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

* **Bug Fixes**
* Workshop requests are now correctly identified as “Website - Workshop
request” in CRM records, improving source tracking and reporting.
* Workshop request contacts are treated as form-based, ensuring the most
recent campaign member status is set to **Registered**.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-30 08:13:01 -05:00
Eric c69bb929d1 Website: update fleet-leave-behind-deck.pdf (#50206)
Changes:
- Updated fleet-leave-behind-deck.pdf to fix an issue where the file was
not loading on Chrome.
2026-07-30 00:13:03 -05:00
Allen HouchinsandMike Thomas 7f07651f97 Add Hawx case study (#50152)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** N/A

# Checklist for submitter

This PR adds one markdown file under `articles/` (a customer case study)
— no product code, so most of the template below doesn't apply.

## What changed

Adds `articles/hawx.md`, a case study on Hawx Pest Control.

**The story:** Hawx is a technology-first pest control company whose
field technicians can't be dispatched without a provisioned phone.
Hiring ramps up hard every summer, so onboarding and offboarding run
constantly. With Jamf, phones sat on the MDM screen until the technician
logged in, nobody remembered their credentials, and the helpdesk got
flooded from personal phones every season. Identifying which device
belonged to which technician took 5 to 10 minutes per call, 5 to 10
calls a day. Hawx now drives Fleet entirely through the API, paired with
Tines and Okta, so a device lands in the right fleet with the correct
profiles, policies, and apps the moment the technician verifies their
identity. Offboarding wipes or locks based on role. Migration took a
month.

**Source:** the 2026-07-20 customer interview with Loren Farr, IT
Manager at Hawx. Loren confirmed on the call that Fleet may use the
company name and his name and title, and was told nothing publishes
without his approval.

**Format:** drafted against the proposed `fleet-case-study-formatting`
skill in #49917 and the `content-style` skill:

- Three-act narrative (the challenge → why Fleet → the solution → the
results), headings in sentence case.
- Four `attribution-quote` divs spaced through the narrative rather than
clustered.
- A `checklist` div for the headline results.
- Full endmatter including the build-enforced `summaryChallenge` /
`summarySolution` / `summaryKeyResults` (semicolon-separated) plus the
company and hero-quote tags.
- "About Hawx" lives in `companyInfo` / `companyInfoLineTwo` rather than
the body, matching every published case study.

## Why

Hawx is a strong story in a segment Fleet's published case studies don't
yet cover: iOS-only, a small IT team (3 people, ~500 devices), a
seasonal workforce, and an API-only usage pattern where the customer
never touches the Fleet UI. It's also a clean Jamf migration narrative
with a quantified helpdesk result.

## ⚠️ Blockers before this can be published

This is a **draft PR on purpose**. Two items must be resolved first:

1. **The quotes are not verbatim yet.** The interview record is bullet
notes, not a transcript, so the four quotes are faithful reconstructions
of what Loren described, not transcribed speech. The case-study skill's
rule is that quotes are verbatim and never reconstructed. **Loren needs
to approve these as his words before merge.** Reviewers should not treat
them as citable until he has.
2. **Both image assets are missing.** There is no Hawx logo and no Loren
Farr headshot in `website/assets/images/`. `companyLogoFilename` and
`quoteAuthorImageFilename` are deliberately stubbed with `TODO-`
prefixes so the website build fails loudly rather than shipping broken
image references. Real files are needed following the
`{descriptor}-{css-width}x{css-height}@2x.{ext}` convention.

## Open questions for reviewers

- **The "more than 90%" figure was dropped.** An earlier draft said
credentials were forgotten in more than 90% of cases. That number isn't
in the interview notes (the notes say "nobody knew their username or
password"), so it's omitted. If Loren sourced it, it can go back in.
- **The warehoused-device problem is omitted.** The notes describe
devices offline more than 30 days needing a reset, currently a 30-minute
call, listed as a *current* problem. That would fit a "Looking ahead"
section if Fleet is the plan for it, but it doesn't belong in results as
an achieved outcome.
- **Hero quote choice.** `quoteContent` uses the "slam dunk ... control
over the phone itself" quote because it names the differentiator.
Loren's closer, "As long as you're not shy about getting into the code,
this is a fantastic platform," is arguably the more trustworthy line for
Fleet's audience. Easy swap if marketing prefers it.
- **Follow-up, not in this PR:** the pull quote could be added to
`handbook/company/testimonials.yml` for the `/customers` carousel. Left
alone since that file is curated by marketing.

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`. — N/A, website content
only, not a product change

## Testing

- [ ] Added/updated automated tests — N/A, markdown content only
- [x] QA'd manually: cross-checked the structure, custom div syntax, and
required meta tags against the published case studies
(`articles/fastly.md`, `articles/primo.md`) and against the validation
logic in `website/scripts/build-static-content.js`; confirmed
`summaryKeyResults` is semicolon-separated; confirmed `articleTitle`
matches the H1 exactly; grepped `website/assets/images/` and confirmed
both referenced image files are absent (hence the `TODO-` stubs).

---------

Co-authored-by: Mike Thomas <78363703+mike-j-thomas@users.noreply.github.com>
2026-07-30 11:03:23 +09:00
Mike Thomas bf95fda79e Website: update sales leave-behind deck (#50123)
**Related issue:** NA

# Checklist for submitter

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

---

Updates the leave-behind deck with revisions from design review:

* Updated headings on slide 2 to address pain sooner
* Moved "The world has evolved rapidly" heading to slide 3
* Incorporated platform icons into illustration graphic
2026-07-30 07:53:09 +09:00
fb06efcaac Website: redesign /support page with unified card grid and testimonial (#50132)
**Related issue:** Related to fleetdm/confidential#16762 (not a full
close — see Notes)

# Checklist for submitter

## Testing

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

## Summary
Redesigns fleetdm.com/support:
- Merges the previous "Ask the community" and "Support" sections into
one unified "How can we help?" section with a single 6-card grid
- Adds a new "Professional support" card linking to the SLA section of
the go-to-market-operations handbook page
- Adds a customer testimonial
- Consolidates responsive breakpoints (single-column switch and card
centering now both happen at 991px)
- Removes target="_blank" from the two cards linking to fleetdm.com
itself (Professional support, Ask us anything).

## Related
- fleetdm/confidential#16762

## Notes
This is a first iteration toward the broader vision in
https://github.com/fleetdm/confidential/issues/16762, discussed in
design review but intentionally deferred.


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

## Summary by CodeRabbit

* **New Features**
* Redesigned the Support page header into a single “How can we help?”
entry point.
* Added a Professional Support card linking to service-level agreement
details.
  * Introduced a customer quote section with author information.

* **Improvements**
* Refreshed support card design (cleaner layout, consistent spacing,
updated typography and link hover behavior).

* **Responsive**
* Improved the support card layout across breakpoints, including
3-column to 2-column to single-column grid behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Eric <eashaw@sailsjs.com>
2026-07-29 17:18:19 -05:00
Eric bd419cd2de Website: update fallback talk to us meeting link (#50182)
Changes:
- Updated deliver-talk-to-us-form-submission to return a different
Salesforce user ID if territory information could not be found.

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

* **Bug Fixes**
* Updated the fallback “Talk to us” form submission contact-routing
behavior when territory information is unavailable or returns an
unexpected value, including changes to the default Calendly meeting link
and related warning messaging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-29 15:10:51 -05:00
Eric f25b1ec58d Website: update territory routing for talk to us submissions (#50101)
https://github.com/fleetdm/confidential/issues/16947

Changes:
- Updated the get-territory-user-id helper to accept `website` and
`numberOfEmployees` inputs
- Updated the deliver-talk-to-us-form-submission action to send website
and number of employees to the get-territory-user-id helper, and updated
the `bookingUrlByUserId` dictionary.

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved territory routing for “Talk to us” submissions by using
employee count, city, and website details.
* Updated routing logic to direct submissions to the appropriate
Calendly booking link.
* Preserved existing validation and error handling for incomplete or
unexpected territory lookup results.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-29 13:48:08 -05:00
Noah Talerman 40bc83daa9 Add rachaelshaw (API design DRI) as auto reviewer for usage stasts (#50169)
- Context:
https://github.com/fleetdm/fleet/pull/49980#issuecomment-5121775909
2026-07-29 11:41:44 -07:00