Add Codex CLI as a Windows FMA (#42397)

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

## Summary by CodeRabbit

* **New Features**
* Added comprehensive support for managing Codex CLI (OpenAI's coding
agent) on Windows systems, including automated installation,
uninstallation, and verification that installed binaries match expected
versions
* Integrated Codex CLI icon component into the software interface for
improved visual identification and enhanced user experience when
managing this application

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/42397)

<!-- review_stack_entry_end -->

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Allen Houchins
2026-05-15 13:20:10 -05:00
committed by GitHub
co-authored by copilot-swe-agent[bot] Cursor
parent 19de907416
commit 9afdb43567
10 changed files with 203 additions and 0 deletions
+68
View File
@@ -174,6 +174,74 @@ func appExists(ctx context.Context, logger *slog.Logger, appName, uniqueIdentifi
}
}
// OpenAI Codex CLI is a portable zip: it does not register in programs. Detect the binary via osquery file + PE version.
if uniqueIdentifier == "Codex CLI" {
ok, err := codexCLIExistsFromFile(execTimeout, logger, appVersion, appPath)
if err != nil {
return false, err
}
if ok {
return true, nil
}
}
return false, nil
}
func codexCLIExistsFromFile(ctx context.Context, logger *slog.Logger, appVersion, appPath string) (bool, error) {
candidates := make([]string, 0, 3)
if appPath != "" {
candidates = append(candidates, filepath.Join(appPath, "codex.exe"))
}
if pf := os.Getenv("ProgramFiles"); pf != "" {
candidates = append(candidates, filepath.Join(pf, "Codex CLI", "codex.exe"))
}
if la := os.Getenv("LOCALAPPDATA"); la != "" {
candidates = append(candidates, filepath.Join(la, "Programs", "Codex CLI", "codex.exe"))
}
seen := make(map[string]struct{})
for _, exePath := range candidates {
if _, dup := seen[exePath]; dup {
continue
}
seen[exePath] = struct{}{}
if err := validateSqlInput(exePath); err != nil {
continue
}
escaped := strings.ReplaceAll(exePath, "'", "''")
query := `SELECT file_version FROM file WHERE path = '` + escaped + `'`
cmd := exec.CommandContext(ctx, "osqueryi", "--json", query)
output, err := cmd.CombinedOutput()
if err != nil {
logger.ErrorContext(ctx, fmt.Sprintf("osquery output: %s", string(output)))
return false, fmt.Errorf("executing osquery file lookup: %w", err)
}
type fileResult struct {
FileVersion string `json:"file_version"`
}
var results []fileResult
if err := json.Unmarshal(output, &results); err != nil {
logger.ErrorContext(ctx, fmt.Sprintf("osquery output: %s", string(output)))
return false, fmt.Errorf("parsing osquery JSON output: %w", err)
}
if len(results) == 0 || results[0].FileVersion == "" {
continue
}
fileVer := results[0].FileVersion
logger.InfoContext(ctx, fmt.Sprintf("Found Codex CLI binary at %s, file version: %s", exePath, fileVer))
if fileVer == appVersion ||
strings.HasPrefix(fileVer, appVersion+".") ||
strings.HasPrefix(appVersion, fileVer+".") {
return true, nil
}
}
return false, nil
}
@@ -381,6 +381,9 @@ func (i *wingetIngester) ingestOne(ctx context.Context, input inputApp) (*mainta
}
out.Queries = setUpExistsQuery(input.FuzzyMatchName, name, publisher)
if input.ExistsQuery != "" {
out.Queries.Exists = input.ExistsQuery
}
out.InstallScript = installScript
processedUninstallScript, err := preProcessUninstallScript(uninstallScript, productCode)
if err != nil {
@@ -515,6 +518,8 @@ type inputApp struct {
ProgramPublisher string `json:"program_publisher"`
UninstallType string `json:"uninstall_type"`
FuzzyMatchName fuzzyMatch `json:"fuzzy_match_name"`
// ExistsQuery overrides the default programs-table exists query (e.g. portable zip installs).
ExistsQuery string `json:"exists_query,omitempty"`
// Whether to use "no_check" instead of the app's hash (e.g. for non-pinned download URLs)
IgnoreHash bool `json:"ignore_hash"`
DefaultCategories []string `json:"default_categories"`
@@ -0,0 +1,13 @@
{
"name": "Codex CLI",
"slug": "codex-cli/windows",
"package_identifier": "OpenAI.Codex",
"unique_identifier": "Codex CLI",
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/codex-cli_install.ps1",
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/codex-cli_uninstall.ps1",
"exists_query": "SELECT 1 FROM file WHERE path = 'C:\\Program Files\\Codex CLI\\codex.exe' OR path LIKE '%\\AppData\\Local\\Programs\\Codex CLI\\codex.exe';",
"installer_arch": "x64",
"installer_type": "zip",
"installer_scope": "user",
"default_categories": ["Developer tools"]
}
@@ -0,0 +1,59 @@
# Codex ships as a .zip of portable binaries (winget NestedInstallerType: portable).
# INSTALLER_PATH points at the downloaded .zip.
# Prefer Program Files (matches Fleet validation + managed installs); fall back to %LOCALAPPDATA% without admin.
$ErrorActionPreference = "Stop"
$zipPath = "${env:INSTALLER_PATH}"
$machineRoot = Join-Path $env:ProgramFiles "Codex CLI"
$userRoot = Join-Path $env:LOCALAPPDATA "Programs\Codex CLI"
$extractDir = Join-Path $env:TEMP ("codex-winget-extract-" + [Guid]::NewGuid().ToString())
if (-not (Test-Path -LiteralPath $machineRoot)) {
try {
New-Item -ItemType Directory -Path $machineRoot -Force -ErrorAction Stop | Out-Null
$installRoot = $machineRoot
} catch {
New-Item -ItemType Directory -Path $userRoot -Force | Out-Null
$installRoot = $userRoot
}
} else {
$installRoot = $machineRoot
}
try {
if (-not (Test-Path -LiteralPath $zipPath)) {
Write-Host "Installer not found: $zipPath"
Exit 1
}
New-Item -ItemType Directory -Path $installRoot -Force | Out-Null
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force
# Matches winget manifest NestedInstallerFiles for x64
$mainExe = Join-Path $extractDir "codex-x86_64-pc-windows-msvc.exe"
if (-not (Test-Path -LiteralPath $mainExe)) {
Write-Host "Expected binary codex-x86_64-pc-windows-msvc.exe not found in archive"
Exit 1
}
$destExe = Join-Path $installRoot "codex.exe"
Copy-Item -LiteralPath $mainExe -Destination $destExe -Force
foreach ($extra in @(
"codex-command-runner.exe",
"codex-windows-sandbox-setup.exe"
)) {
$src = Join-Path $extractDir $extra
if (Test-Path -LiteralPath $src) {
Copy-Item -LiteralPath $src -Destination (Join-Path $installRoot $extra) -Force
}
}
Exit 0
} catch {
Write-Host "Error: $_"
Exit 1
} finally {
Remove-Item -LiteralPath $extractDir -Recurse -Force -ErrorAction SilentlyContinue
}
@@ -0,0 +1,13 @@
# Removes layouts created by codex-cli_install.ps1 (machine and per-user fallbacks).
$ErrorActionPreference = "Continue"
foreach ($installRoot in @(
(Join-Path $env:ProgramFiles "Codex CLI"),
(Join-Path $env:LOCALAPPDATA "Programs\Codex CLI")
)) {
if (Test-Path -LiteralPath $installRoot) {
Remove-Item -LiteralPath $installRoot -Recurse -Force
}
}
Exit 0
+7
View File
@@ -463,6 +463,13 @@
"unique_identifier": "Cloudflare WARP",
"description": "Cloudflare WARP enhances internet safety and performance by encrypting your data and optimizing connections for privacy."
},
{
"name": "Codex CLI",
"slug": "codex-cli/windows",
"platform": "windows",
"unique_identifier": "Codex CLI",
"description": "Codex CLI is OpenAIs coding agent that you can run locally from your terminal."
},
{
"name": "Connect Fonts",
"slug": "connect-fonts/darwin",
@@ -0,0 +1,22 @@
{
"versions": [
{
"version": "0.116.0",
"queries": {
"exists": "SELECT 1 FROM file WHERE path = 'C:\\Program Files\\Codex CLI\\codex.exe' OR path LIKE '%\\AppData\\Local\\Programs\\Codex CLI\\codex.exe';",
"patch": ""
},
"installer_url": "https://github.com/openai/codex/releases/download/rust-v0.116.0/codex-x86_64-pc-windows-msvc.exe.zip",
"install_script_ref": "8bb4b68b",
"uninstall_script_ref": "7e1fe544",
"sha256": "f3a64ca3d389224404c1eb654015a868247e4f59bd300e3cdc05f8933f667f8a",
"default_categories": [
"Developer tools"
]
}
],
"refs": {
"7e1fe544": "# Removes layouts created by codex-cli_install.ps1 (machine and per-user fallbacks).\n\n$ErrorActionPreference = \"Continue\"\nforeach ($installRoot in @(\n (Join-Path $env:ProgramFiles \"Codex CLI\"),\n (Join-Path $env:LOCALAPPDATA \"Programs\\Codex CLI\")\n)) {\n if (Test-Path -LiteralPath $installRoot) {\n Remove-Item -LiteralPath $installRoot -Recurse -Force\n }\n}\n\nExit 0\n",
"8bb4b68b": "# Codex ships as a .zip of portable binaries (winget NestedInstallerType: portable).\n# INSTALLER_PATH points at the downloaded .zip.\n# Prefer Program Files (matches Fleet validation + managed installs); fall back to %LOCALAPPDATA% without admin.\n\n$ErrorActionPreference = \"Stop\"\n$zipPath = \"${env:INSTALLER_PATH}\"\n$machineRoot = Join-Path $env:ProgramFiles \"Codex CLI\"\n$userRoot = Join-Path $env:LOCALAPPDATA \"Programs\\Codex CLI\"\n$extractDir = Join-Path $env:TEMP (\"codex-winget-extract-\" + [Guid]::NewGuid().ToString())\n\nif (-not (Test-Path -LiteralPath $machineRoot)) {\n try {\n New-Item -ItemType Directory -Path $machineRoot -Force -ErrorAction Stop | Out-Null\n $installRoot = $machineRoot\n } catch {\n New-Item -ItemType Directory -Path $userRoot -Force | Out-Null\n $installRoot = $userRoot\n }\n} else {\n $installRoot = $machineRoot\n}\n\ntry {\n if (-not (Test-Path -LiteralPath $zipPath)) {\n Write-Host \"Installer not found: $zipPath\"\n Exit 1\n }\n\n New-Item -ItemType Directory -Path $installRoot -Force | Out-Null\n New-Item -ItemType Directory -Path $extractDir -Force | Out-Null\n Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force\n\n # Matches winget manifest NestedInstallerFiles for x64\n $mainExe = Join-Path $extractDir \"codex-x86_64-pc-windows-msvc.exe\"\n if (-not (Test-Path -LiteralPath $mainExe)) {\n Write-Host \"Expected binary codex-x86_64-pc-windows-msvc.exe not found in archive\"\n Exit 1\n }\n\n $destExe = Join-Path $installRoot \"codex.exe\"\n Copy-Item -LiteralPath $mainExe -Destination $destExe -Force\n\n foreach ($extra in @(\n \"codex-command-runner.exe\",\n \"codex-windows-sandbox-setup.exe\"\n )) {\n $src = Join-Path $extractDir $extra\n if (Test-Path -LiteralPath $src) {\n Copy-Item -LiteralPath $src -Destination (Join-Path $installRoot $extra) -Force\n }\n }\n\n Exit 0\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n} finally {\n Remove-Item -LiteralPath $extractDir -Recurse -Force -ErrorAction SilentlyContinue\n}\n"
}
}
File diff suppressed because one or more lines are too long
@@ -9,6 +9,7 @@ import Backblaze from "./Backblaze";
import BetterDisplay from "./BetterDisplay";
import Cavalry from "./Cavalry";
import Charles from "./Charles";
import CodexCli from "./CodexCli";
import ConnectFonts from "./ConnectFonts";
import CrashPlan from "./CrashPlan";
import DruvaInSync from "./DruvaInSync";
@@ -320,6 +321,7 @@ export const SOFTWARE_NAME_TO_ICON_MAP = {
"clockify desktop": ClockifyDesktop,
cloudflare: Cloudflare,
code: VisualStudioCode,
"codex cli": CodexCli,
"company portal": IntuneCompanyPortal,
"connect fonts": ConnectFonts,
crashplan: CrashPlan,
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB