Files
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

186 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 isnt 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 vendors documented silent uninstall switch or the registered UninstallString (if available), ensuring the script runs silently and returns the installers 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 doesnt 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 crossplatform.
- 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.