<!-- 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 -->
186 lines
18 KiB
Markdown
186 lines
18 KiB
Markdown
# Fleet-maintained apps (FMA)
|
||
|
||
> **Using Claude Code?** The `new-fma` skill ([.claude/skills/new-fma/SKILL.md](../../.claude/skills/new-fma/SKILL.md)) automates this workflow and bakes in the gotchas this README doesn't cover — verifying the installed app's identity with `msiinfo`/`PlistBuddy` instead of trusting winget/cask metadata, handling bootstrapper installers, parsing unquoted `UninstallString`s, version-matching quirks, and more. Just ask it to "add X as a macOS/Windows FMA."
|
||
|
||
## Adding a new app (macOS)
|
||
|
||
1. Find the app's metadata in its [Homebrew formulae](https://formulae.brew.sh/)
|
||
2. Create a new manifest file called `$YOUR_APP_NAME.json` in the `inputs/homebrew/` directory. For
|
||
example, if you wanted to add Box Drive, create the file `inputs/homebrew/box-drive.json`.
|
||
3. Fill out the file according to the [input schema below](#macos-input-file-schema). For our example Box Drive app, it would look like this:
|
||
|
||
```json
|
||
{
|
||
"name": "Box Drive",
|
||
"slug": "box-drive/darwin",
|
||
"unique_identifier": "com.box.desktop",
|
||
"token": "box-drive",
|
||
"installer_format": "pkg",
|
||
"default_categories": ["Productivity"]
|
||
}
|
||
```
|
||
|
||
4. Run the following command from the root of the Fleet repo to generate the app's output data:
|
||
|
||
```bash
|
||
go run cmd/maintained-apps/main.go --slug="<slug-name>" --debug
|
||
```
|
||
|
||
5. The contributor is responsible for adding the icon to Fleet (e.g. the TypeScript and website PNG components of [#29175](https://github.com/fleetdm/fleet/pull/29175/files)). These are generated using the [generate-icons](https://github.com/fleetdm/fleet/tree/main/tools/software/icons) script. **The script automatically adds the import statement and map entry to `frontend/pages/SoftwarePage/components/icons/index.ts`**, so you don't need to manually update the index file.
|
||
|
||
6. Add a description for the app in `outputs/apps.json` file. You can use descriptions from [Homebrew formulae](https://formulae.brew.sh/). For consistency and presentation on the website, the description should follow sentence casing and the following format: `<App Name>` is a(n) (copy description from Homebrew)., making sure to end with a `.`.
|
||
|
||
7. Open a PR to the `fleet` repository with the above changes. The [#g-software Engineering Manager (EM)](https://fleetdm.com/handbook/company/product-groups#software-group) is automatically added reviewer. Also, @ mention the [Fleet-maintained apps DRI](https://fleetdm.com/handbook/company/communications#:~:text=Fleet%2Dmaintained%20apps).
|
||
|
||
8. If the app passes automated tests, it is approved and merged. The EM reviews the PR within 3 business days. The app should appear shortly in the Fleet-maintained apps section when adding new software to Fleet. The app icon will not appear in Fleet until the following release.
|
||
|
||
### macOS input file schema
|
||
|
||
| Name | Type | Description |
|
||
|--------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||
| `name` | string | **Required.** User-facing name of the application. |
|
||
| `unique_identifier` | string | **Required.** Platform-specific unique identifier (e.g., bundle identifier on macOS). |
|
||
| `token` | string | **Required.** Homebrew's unique identifier. It's the `token` field of the Homebrew API response. |
|
||
| `installer_format` | string | **Required.** File format of the installer (`zip`, `dmg`, `pkg`). Determine via the file extension in the Homebrew API `url` field or by downloading the installer if the extension isn’t present. |
|
||
| `slug` | string | **Required.** Identifies the app/platform combination (e.g., `box-drive/darwin`). Used to name manifest files and reference the app in [Fleet's best practice GitOps](https://fleetdm.com/docs/configuration/yaml-files#fleet-maintained-apps). Format: `<app-name>/<platform>`, where app name is filesystem-friendly and platform is `darwin`. |
|
||
| `default_categories` | string | **Required.** Default categories for self-service if none are specified. Valid values: `Browsers`, `Communication`, `Developer Tools`, `Productivity`. |
|
||
| `pre_uninstall_scripts` | string | Command lines run **before** the generated uninstall script (e.g., for [Box](inputs/homebrew/box-drive.json)). |
|
||
| `post_uninstall_scripts` | string | Command lines run **after** the generated uninstall script (e.g., for [Box](inputs/homebrew/box-drive.json)). |
|
||
| `install_script_path` | string | Filepath to a custom install script (`.sh`). Overrides the generated install script. Script must be placed in `inputs/homebrew/scripts/`. |
|
||
| `uninstall_script_path` | string | Filepath to a custom uninstall script (`.sh`). Overrides the generated uninstall script. Cannot be used together with `pre_uninstall_scripts` or `post_uninstall_scripts`. Script must be placed in `inputs/homebrew/scripts/`. |
|
||
| `cask_path` | string | Path (relative to the repo root) to a local file containing the cask JSON in the same schema as `https://formulae.brew.sh/api/cask/<token>.json`. Used to commit cask metadata for third-party taps directly into this repo under [`inputs/homebrew/custom-tap/`](inputs/homebrew/custom-tap/). See [Ingesting apps from a custom tap](#ingesting-apps-from-a-custom-tap) below. |
|
||
|
||
### Ingesting apps from a custom tap
|
||
|
||
Apps that live in a third-party Homebrew tap (not `Homebrew/homebrew-cask`) are not proxied by `https://formulae.brew.sh/api/`. To ingest them, commit both the `.rb` source and the generated `.json` into [`inputs/homebrew/custom-tap/`](inputs/homebrew/custom-tap/), laid out like a Homebrew tap:
|
||
|
||
```
|
||
custom-tap/
|
||
├── Casks/<token>.rb # Cask DSL source
|
||
├── api/<token>.json # Generated with regenerate.sh
|
||
└── regenerate.sh # Rebuild api/*.json from Casks/*.rb
|
||
```
|
||
|
||
1. Write the cask DSL in `inputs/homebrew/custom-tap/Casks/<token>.rb`.
|
||
2. Run `./regenerate.sh` from inside `custom-tap/` to produce `api/<token>.json`. Requires macOS with Homebrew and `jq`.
|
||
3. In the app's input manifest (`inputs/homebrew/<token>.json`), set `cask_path` to `ee/maintained-apps/inputs/homebrew/custom-tap/api/<token>.json`. See `inputs/homebrew/fleet-desktop.json` for an example.
|
||
|
||
See [`inputs/homebrew/custom-tap/README.md`](inputs/homebrew/custom-tap/README.md) for the full contributor flow. Apps without `cask_path` continue to be fetched from `formulae.brew.sh`.
|
||
|
||
## Adding a new app (Windows)
|
||
|
||
1. Find the Winget `PackageIdentifier` in the relevant [winget-pkgs repo manifest](https://github.com/microsoft/winget-pkgs/tree/master/manifests).
|
||
|
||
2. Get the unique identifier that Fleet will use for matching the software with software inventory:
|
||
- On a test Windows host, install the app manually, then run the following PowerShell script that correlates to the defined `installer_scope`:
|
||
- Machine scope: `Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -like '*<App Name>*'} | Select-Object DisplayName, DisplayVersion, Publisher`
|
||
- User scope: `Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -like '*<App Name>*'} | Select-Object DisplayName, DisplayVersion, Publisher`
|
||
|
||
If the `unique_identifier` doesn't match the `DisplayName`, then Fleet will incorrectly create two software titles when the Fleet-maintained app is added and later installed. One title for the Fleet-maintained app and a separate title for the inventoried software.
|
||
|
||
3. Fill out the file according to the [input schema](#windows-input-file-schema). For example, Box Drive looks like this:
|
||
|
||
```json
|
||
{
|
||
"name": "Box Drive",
|
||
"slug": "box-drive/windows",
|
||
"package_identifier": "Box.Box",
|
||
"unique_identifier": "Box",
|
||
"installer_arch": "x64",
|
||
"installer_type": "msi",
|
||
"installer_scope": "machine",
|
||
"default_categories": ["Productivity"]
|
||
}
|
||
```
|
||
|
||
|
||
4. Run `go run cmd/maintained-apps/main.go --slug="<app-name>/windows" --debug` from the root of the
|
||
Fleet repo to generate the app's output data, replacing `<app-name>` with your app's name, for example:
|
||
|
||
```bash
|
||
go run cmd/maintained-apps/main.go --slug="box-drive/windows" --debug
|
||
```
|
||
|
||
5. The contributor is responsible for adding the icon to Fleet (e.g. the TypeScript and website PNG components of [#29175](https://github.com/fleetdm/fleet/pull/29175/files)). These are generated using the [generate-icons](https://github.com/fleetdm/fleet/tree/main/tools/software/icons) script. **The script automatically adds the import statement and map entry to `frontend/pages/SoftwarePage/components/icons/index.ts`**, so you don't need to manually update the index file.
|
||
|
||
6. Add a description for the app in outputs/apps.json file. You can use descriptions from the wingest manifest.
|
||
|
||
7. Open a PR to the fleet repository with the above changes. The [#g-software Engineering Manager (EM)](https://fleetdm.com/handbook/company/product-groups#software-group) is automatically added reviewer. Also, @ mention the #g-software Product Designer (PD) in a comment that points them to the new icon. This way, the icon change gets a second pair of eyes.
|
||
|
||
8. If the app passes automated tests, it is approved and merged. The EM reviews the PR within 3 business days. The app should appear shortly in the Fleet-maintained apps section when adding new software to Fleet. The app icon will not appear in Fleet until the following release.
|
||
|
||
### Windows input file schema
|
||
|
||
| Name | Type | Description |
|
||
|--------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||
| `name` | string | **Required.** User-facing name of the application. |
|
||
| `unique_identifier` | string | **Required.** Platform-specific unique identifier. For Windows, this is the `DisplayName`. |
|
||
| `package_identifier` | string | **Required.** The `PackageIdentifier` from winget. Fleet uses this to pull the correct metadata for the app. |
|
||
| `slug` | string | **Required.** Identifies the app/platform combination (e.g., `box-drive/windows`). Used to name manifest files and reference the app in [Fleet's best practice GitOps](https://fleetdm.com/docs/configuration/yaml-files#fleet-maintained-apps). Format: `<app-name>/<platform>`, where app name is filesystem-friendly and platform is `darwin`. |
|
||
| `installer_arch` | string | **Required.** `x64` or `x86` (most apps use `x64`). |
|
||
| `installer_type` | string | **Required.** `exe`, `msi`, or `msix` (file type, not vendor tech like "wix") |
|
||
| `installer_scope` | string | **Required.** `machine` or `user` (prefer `machine` for managed installs) |
|
||
| `default_categories` | string | **Required.** Default categories for self-service if none are specified. Valid values: `Browsers`, `Communication`, `Developer Tools`, `Productivity`. |
|
||
| `install_script_path` | string | Filepath to a custom install script (`.ps1`). Overrides the generated install script. Script must be placed in `inputs/winget/scripts/`. For `.msi` apps, the ingestor automatically generates install scripts. Do not add scripts unless you need to override the generated behavior. For `.exe` apps, you must provide PowerShell scripts that run the installer file directly. Fleet stores the installer and sends it to the host at install time; your script must execute it using the `INSTALLER_PATH` environment variable. |
|
||
| `uninstall_script_path` | string | Filepath to a custom uninstall script (`.ps1`). Overrides the generated uninstall script. Script must be placed in `inputs/winget/scripts/`. For `.msi` apps, the ingestor automatically generates uninstall scripts. Do not add scripts unless you need to override the generated behavior. For `.exe` apps, you must provide a script to uninstall the app. Scripts for `.exe` apps are vendor-specific. Use the vendor’s documented silent uninstall switch or the registered UninstallString (if available), ensuring the script runs silently and returns the installer’s exit code. |
|
||
| `fuzzy_match_name` | boolean | If the `unique_identifier` doesn't match the `DisplayName`, use `fuzzy_match_name` to specify that Fleet uses "fuzzy matching" to match the Fleet-maintained app and the inventoried software. For example, for Pritunl, the `unique_identifier` is "Pritunl" and the inventories software's `DisplayName` is "Pritunl Client". With `fuzzy_match_name` set to true, Pritunl app will be matched to the inventories software. |
|
||
| `requires_client_os` | boolean | Set to `true` when the installer refuses to run on Windows Server SKUs (e.g., Dell Display and Peripheral Manager). Fleet's ingestion ignores this field; CI reads it to route validation to the `windows-11-arm` runner (the only GitHub-hosted client-OS Windows runner) instead of the default Windows Server x64 runner. |
|
||
|
||
#### Windows troubleshooting
|
||
|
||
- App not found in Fleet UI: ensure `apps.json` was updated by the generator and your override URL is correct
|
||
- Install fails silently: confirm your `installer_type`, `installer_arch`, and `installer_scope` match the selected winget installer; run your PowerShell script manually on a test host
|
||
- Uninstall doesn’t remove the app: prefer explicit uninstall scripts; otherwise, ensure the winget manifest exposes `ProductCode` or `UpgradeCode`
|
||
- Hash mismatch errors: if the upstream manifest is in flux, you can set `ignore_hash: true` in the input JSON (use sparingly)
|
||
|
||
#### Can I do this on macOS?
|
||
|
||
The instructions below are meant to be run on a Windows host. But, you can run most of this on a macOS host, as well:
|
||
- You can author Windows inputs and run the generator on macOS. The ingester is Go code that fetches data from winget/GitHub and works cross‑platform.
|
||
- To find the PackageName and Publisher, you can look in the locale and installer yaml files in the winget-pkgs repo.
|
||
- Validation and testing still require a Windows host (to verify programs.name and to run install/uninstall).
|
||
|
||
## Updating existing Fleet-maintained apps
|
||
|
||
Fleet-maintained apps need to be updated as frequently as possible while maintaining reliability. This is currently a balancing act as both scenarios below result in customer workflow blocking bugs:
|
||
|
||
- App vendor updates to installers can break install/uninstall scripts
|
||
- App vendors will deprecate download links for older installers
|
||
|
||
A Github action periodically creates a PR that updates one or more apps in the catalog by:
|
||
|
||
- Bumping versions
|
||
- Regenerating install/uninstall scripts
|
||
|
||
Each app updated in the PR must be validated independently. Only merge the PR if all apps changed meet the following criteria:
|
||
|
||
- [X] App can be downloaded using manifest URL
|
||
- [X] App installs successfully on host using manifest install script
|
||
- [X] App exists on host
|
||
- [X] App uninstalls successfully on host using manifest uninstall script
|
||
|
||
If an app does not pass test criteria:
|
||
|
||
- [Freeze the app](#freezing-an-existing-fleet-maintained-app)
|
||
- File a bug for tracking
|
||
|
||
## Freezing an existing Fleet-maintained app
|
||
|
||
If any app fails validation:
|
||
|
||
1. Do not merge the PR as-is.
|
||
2. Add `"frozen": true"` to the failing app's input file (e.g.,`inputs/homebrew/<app>.json`).
|
||
3. Revert its corresponding output manifest file (e.g., `outputs/<slug>.json`) to the version in the `main` branch:
|
||
|
||
```bash
|
||
git checkout origin/main -- ee/maintained-apps/outputs/<slug>.json
|
||
```
|
||
|
||
4. Validate changes in the frozen input file by running the following. This should output no errors and generate no changes.
|
||
|
||
```bash
|
||
go run cmd/maintained-apps/main.go --slug="<slug>" --debug
|
||
```
|
||
|
||
5. Commit both the input change and the output file revert to the same PR.
|