Fix Windows FMA per-user vs per-machine install scope (#48248)#48603
Conversation
Windows apps can install per-user (HKCU, %LOCALAPPDATA%) or per-machine (HKLM, Program Files). The FMA patch policy is scope-blind (osquery's programs table reads both), so when a host had a copy at one scope and the FMA installed at the other, a duplicate was created, the stale copy lingered unmanaged, and the policy never reported patched. Foundation: - Ingester now trusts the input's installer_scope when the winget manifest is silent on scope (previously panicked). Exact matching still selects the right variant for apps that declare per-installer scope (PowerToys, GIMP). - Set installer_scope on the 9 previously-unset winget inputs, verified from each installer's PE requestedExecutionLevel / MSI ALLUSERS / winget Scope. - Added TestInputInstallerScopeIsSet enforcing scope is set on all inputs, plus ingester tests for the scope-fallback and scope-mismatch paths. - Documented the never-scope-narrow guardrail in the FMA authoring guide (README) and the new-fma skill. Reference remediation (Pattern A): - PowerToys and GIMP install scripts remove any stale per-user copy before installing the per-machine copy. Because Fleet runs scripts as SYSTEM (where HKCU is SYSTEM's own hive), they enumerate HKEY_USERS per-user hives. Removal is best-effort: it never aborts the machine install and never goes false-green. Detection queries remain scope-blind. Live Windows-host validation of the Pattern A scripts is still pending (see issue acceptance criteria).
Script Diff Resultsee/maintained-apps/outputs/gimp/windows.json=== Install // 7e5269bc -> 72113c10 ===
--- /tmp/old.8rjuJw 2026-07-01 21:57:09.438576928 +0000
+++ /tmp/new.zxCD4b 2026-07-01 21:57:09.438576928 +0000
@@ -1,5 +1,103 @@
-# Learn more about .exe install scripts:
-# http://fleetdm.com/learn-more-about/exe-install-scripts
+# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
+# GIMP also offers a per-user install, so a host may already have a stale per-user
+# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
+# + every loaded user hive), so a lingering per-user copy keeps the policy red and
+# leaves two copies on disk.
+#
+# Pattern A (remove-and-replace): before installing the machine copy, remove any
+# per-user copy so the device converges on a single canonical copy. The machine
+# installer upgrades an existing machine copy in place, so same-scope data is
+# preserved; only the cross-scope (per-user) copy is removed.
+# See https://github.com/fleetdm/fleet/issues/48248.
+#
+# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
+# hive — NOT the logged-on user's. Per-user copies must be found under
+# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
+# Removal is best-effort: it never aborts the machine install, and a copy that
+# survives keeps the (truthful) scope-blind policy red rather than false-green.
+
+# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
+$displayNameLike = "GIMP 3*"
+
+function Get-UninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
+ return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
+ } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
+ } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
+ return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
+ }
+ return $null
+}
+
+function Remove-OtherScopeCopies {
+ param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+
+ # Per-user uninstall registrations live in the logged-on users' hives.
+ $roots = @()
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+
+ $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) {
+ Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
+ continue
+ }
+
+ $parsed = Get-UninstallExeAndArgs $command
+ if (-not $parsed) {
+ Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
+ continue
+ }
+
+ $exe = $parsed.Exe
+ $uninstallArgs = $parsed.Args
+
+ # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
+ if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
+ if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
+ if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ continue
+ }
+
+ Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uninstallArgs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ $p = Start-Process @opts
+ Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ } catch {
+ # Best effort: never fail the machine install because of other-scope cleanup.
+ Write-Host " WARNING: failed to remove per-user copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+} catch {
+ # Cleanup is best-effort; proceed to install regardless.
+ Write-Host "Warning during per-user cleanup: $_"
+}
$exeFilePath = "${env:INSTALLER_PATH}"
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 1a5ce2ca -> 733f8df6 ===
--- /tmp/old.FX7AHO 2026-07-01 21:57:09.494576571 +0000
+++ /tmp/new.gTYeom 2026-07-01 21:57:09.495576564 +0000
@@ -1,6 +1,117 @@
-$exeFilePath = "${env:INSTALLER_PATH}"
+# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
+# ships a per-user installer (PowerToysUserSetup), so a host may already have a
+# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
+# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
+# the policy red and leaves two copies on disk.
+#
+# Pattern A (remove-and-replace): before installing the machine copy, remove any
+# per-user copy so the device converges on a single canonical copy. The machine
+# installer upgrades an existing machine copy in place, so same-scope data is
+# preserved; only the cross-scope (per-user) copy is removed.
+# See https://github.com/fleetdm/fleet/issues/48248.
+#
+# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
+# hive — NOT the logged-on user's. Per-user copies must be found under
+# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
+# Removal is best-effort: it never aborts the machine install, and a copy that
+# survives keeps the (truthful) scope-blind policy red rather than false-green.
+
$ExpectedExitCodes = @(0, 1641, 3010, 1223)
+function Get-UninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
+ return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
+ } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
+ } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
+ return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
+ }
+ return $null
+}
+
+function Remove-OtherScopeCopies {
+ param(
+ [Parameter(Mandatory = $true)][string]$DisplayNameLike,
+ [string]$PublisherLike = ''
+ )
+
+ # Per-user uninstall registrations live in the logged-on users' hives.
+ $roots = @()
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) {
+ Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
+ continue
+ }
+
+ $parsed = Get-UninstallExeAndArgs $command
+ if (-not $parsed) {
+ Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
+ continue
+ }
+
+ $exe = $parsed.Exe
+ $uninstallArgs = $parsed.Args
+
+ # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
+ if ($exe -match '(?i)msiexec') {
+ $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
+ if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
+ if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
+ if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ } else {
+ if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
+ if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
+ if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ }
+
+ if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ continue
+ }
+
+ Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uninstallArgs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ $p = Start-Process @opts
+ Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ } catch {
+ # Best effort: never fail the machine install because of other-scope cleanup.
+ Write-Host " WARNING: failed to remove per-user copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
+ Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+} catch {
+ # Cleanup is best-effort; proceed to install regardless.
+ Write-Host "Warning during per-user cleanup: $_"
+}
+
+$exeFilePath = "${env:INSTALLER_PATH}"
+
try {
$processOptions = @{
=== Uninstall Script (no changes) === |
Extends the per-user vs per-machine fix from PowerToys/GIMP to every dual-variant Windows Fleet-maintained app. Each install script now removes the stale opposite-scope copy before installing the managed copy, so the scope-blind patch policy can truthfully report the host patched. - Unified all apps onto one canonical, best-effort "Remove-FmaOtherScopeCopies" block: it scans ONLY the opposite scope's uninstall hives (HKEY_USERS for machine-target apps, since Fleet runs as SYSTEM where HKCU is SYSTEM's own hive; HKLM for user-target apps), prefers each vendor's QuietUninstallString verbatim, and never aborts the install or goes false-green. - Machine-target (remove per-user): cursor, firefox, firefox@esr, google-drive, onedrive, visual-studio-code, vivaldi, vscodium, windsurf, powertoys, gimp, and the MSI apps 1password, box-drive, dropbox, github-desktop, google-chrome, microsoft-edge, zoom (MSI apps get a custom install script that runs cleanup then msiexec; uninstall stays upgrade-code based). - User-target (remove per-machine): antigravity-ide, brave-browser, kiro, mullvad-browser. - Per-app DisplayName match + silent-uninstall fallback flags derived from each app's own existing uninstall script. Detection queries remain scope-blind. Not yet validated on a live Windows host; per-app QA still required before merge.
Script Diff Resultsee/maintained-apps/outputs/1password/windows.json=== Install // 8959087b -> b5d4dd9b ===
--- /tmp/old.FSMNT6 2026-07-01 22:13:06.104321395 +0000
+++ /tmp/new.yKb703 2026-07-01 22:13:06.104321395 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "1Password*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/antigravity-ide/windows.json=== Install // 8b15c4d0 -> c7f687a6 ===
--- /tmp/old.oLhyXc 2026-07-01 22:13:06.166321219 +0000
+++ /tmp/new.mtazzu 2026-07-01 22:13:06.166321219 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Antigravity*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$ExpectedExitCodes = @(0, 3010)
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/box-drive/windows.json=== Install // 8959087b -> 653463f0 ===
--- /tmp/old.D5tULW 2026-07-01 22:13:06.226321049 +0000
+++ /tmp/new.yO7exU 2026-07-01 22:13:06.226321049 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Box"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/quiet"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/windows.json=== Install // 9a0c2b15 -> b2a17e99 ===
--- /tmp/old.kUJiG3 2026-07-01 22:13:06.278320902 +0000
+++ /tmp/new.7JYqdW 2026-07-01 22:13:06.279320899 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Brave*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$exitCode = 0
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cursor/windows.json=== Install // 03589b5e -> 79e083fb ===
--- /tmp/old.sn8s0N 2026-07-01 22:13:06.329320757 +0000
+++ /tmp/new.0hMQHm 2026-07-01 22:13:06.329320757 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Cursor*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dropbox/windows.json=== Install // 8959087b -> 65bc4efa ===
--- /tmp/old.ujvLpz 2026-07-01 22:13:06.384320602 +0000
+++ /tmp/new.f5MWh8 2026-07-01 22:13:06.385320599 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Dropbox*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox/windows.json=== Install // 80fb9175 -> d6bc46f1 ===
--- /tmp/old.m4kBrz 2026-07-01 22:13:06.440320443 +0000
+++ /tmp/new.r7ArNM 2026-07-01 22:13:06.440320443 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox (x64*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox@esr/windows.json=== Install // 36995f4f -> 389738de ===
--- /tmp/old.DYpI66 2026-07-01 22:13:06.497320282 +0000
+++ /tmp/new.C87RWr 2026-07-01 22:13:06.497320282 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox ESR*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/gimp/windows.json=== Install // 72113c10 -> 3816f627 ===
--- /tmp/old.2vaZAB 2026-07-01 22:13:06.547320140 +0000
+++ /tmp/new.xq0Ewq 2026-07-01 22:13:06.547320140 +0000
@@ -1,48 +1,55 @@
-# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
-# GIMP also offers a per-user install, so a host may already have a stale per-user
-# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
-# + every loaded user hive), so a lingering per-user copy keeps the policy red and
-# leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GIMP 3*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
-# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
-$displayNameLike = "GIMP 3*"
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
- param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -50,55 +57,60 @@
$key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
if (-not $key.DisplayName) { continue }
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
- if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
- if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
- if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
- if (-not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
+# Learn more about .exe install scripts:
+# http://fleetdm.com/learn-more-about/exe-install-scripts
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/github-desktop/windows.json=== Install // 8959087b -> 9fe80ace ===
--- /tmp/old.TrCPjC 2026-07-01 22:13:06.599319993 +0000
+++ /tmp/new.8CLFZq 2026-07-01 22:13:06.599319993 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GitHub Desktop*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "-s"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-chrome/windows.json=== Install // 8959087b -> 29e7984f ===
--- /tmp/old.jIdJrw 2026-07-01 22:13:06.648319854 +0000
+++ /tmp/new.YNoDRe 2026-07-01 22:13:06.649319851 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Chrome*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-drive/windows.json=== Install // fa36b892 -> 8e24f67d ===
--- /tmp/old.y4bO7l 2026-07-01 22:13:06.704319695 +0000
+++ /tmp/new.ShP2hB 2026-07-01 22:13:06.704319695 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Drive"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent --force_stop"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/kiro/windows.json=== Install // c5bc3c21 -> d7705b90 ===
--- /tmp/old.A4j5XV 2026-07-01 22:13:06.761319534 +0000
+++ /tmp/new.wf0tMw 2026-07-01 22:13:06.761319534 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Kiro*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-edge/windows.json=== Install // 8959087b -> e460919b ===
--- /tmp/old.bNSxYQ 2026-07-01 22:13:06.812319389 +0000
+++ /tmp/new.tj8qST 2026-07-01 22:13:06.812319389 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Edge*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/mullvad-browser/windows.json=== Install // de703749 -> 00b6fdaa ===
--- /tmp/old.3p5CoI 2026-07-01 22:13:06.868319231 +0000
+++ /tmp/new.x4cj2g 2026-07-01 22:13:06.868319231 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Mullvad Browser*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install // ab0b56ab -> 991bcdbd ===
--- /tmp/old.rJJsjF 2026-07-01 22:13:06.924319072 +0000
+++ /tmp/new.7KK4Z4 2026-07-01 22:13:06.924319072 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft OneDrive*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 733f8df6 -> 47adf288 ===
--- /tmp/old.JWKyEu 2026-07-01 22:13:06.976318925 +0000
+++ /tmp/new.WTrHzI 2026-07-01 22:13:06.976318925 +0000
@@ -1,50 +1,55 @@
-# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
-# ships a per-user installer (PowerToysUserSetup), so a host may already have a
-# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
-# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
-# the policy red and leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "PowerToys*"
+$fmaPublisherLike = "Microsoft Corporation*"
+$fmaFallbackUninstallArgs = "/uninstall /quiet /norestart"
-$ExpectedExitCodes = @(0, 1641, 3010, 1223)
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
+function Remove-FmaOtherScopeCopies {
param(
- [Parameter(Mandatory = $true)][string]$DisplayNameLike,
- [string]$PublisherLike = ''
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
)
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -54,63 +59,57 @@
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
- if ($exe -match '(?i)msiexec') {
- $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
- if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
- if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
- } else {
- if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
- if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
}
- if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
- Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
+$ExpectedExitCodes = @(0, 1641, 3010, 1223)
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/visual-studio-code/windows.json=== Install // 49122823 -> 2c16ed6d ===
--- /tmp/old.RQ9to4 2026-07-01 22:13:07.031318769 +0000
+++ /tmp/new.1M8WiY 2026-07-01 22:13:07.031318769 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Visual Studio Code*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // d5d49757 -> d9389dbd ===
--- /tmp/old.7SSS6B 2026-07-01 22:13:07.086318613 +0000
+++ /tmp/new.nRo5l8 2026-07-01 22:13:07.086318613 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Vivaldi*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Install Vivaldi silently, machine-wide (Chromium-based browser).
# Fleet runs installs as SYSTEM, so --system-level is required to install for
# all users under %ProgramFiles% (and register under HKLM). Without it the
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vscodium/windows.json=== Install // 38852240 -> 808c2033 ===
--- /tmp/old.gsE2z5 2026-07-01 22:13:07.159318407 +0000
+++ /tmp/new.V15KGO 2026-07-01 22:13:07.160318404 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "VSCodium*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windsurf/windows.json=== Install // 232abd06 -> 044c2bcd ===
--- /tmp/old.mhF0lj 2026-07-01 22:13:07.232318200 +0000
+++ /tmp/new.NSmrsc 2026-07-01 22:13:07.232318200 +0000
@@ -1,3 +1,112 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Windsurf*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
# install switches:
# /VERYSILENT = no UI at all
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zoom/windows.json=== Install // 8959087b -> a6f3a656 ===
--- /tmp/old.VYaYBs 2026-07-01 22:13:07.287318044 +0000
+++ /tmp/new.OHe61P 2026-07-01 22:13:07.288318041 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Zoom*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) === |
Ensure Windows Fleet-maintained apps declare an explicit install scope and remove any opposite-scope copy before installation.
Script Diff Resultsee/maintained-apps/outputs/1password/windows.json=== Install // 8959087b -> b5d4dd9b ===
--- /tmp/old.9sVOsf 2026-07-01 22:14:56.353230594 +0000
+++ /tmp/new.Ff6YAF 2026-07-01 22:14:56.354230617 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "1Password*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/antigravity-ide/windows.json=== Install // 8b15c4d0 -> c7f687a6 ===
--- /tmp/old.C1MqV1 2026-07-01 22:14:56.428232329 +0000
+++ /tmp/new.9cxRg3 2026-07-01 22:14:56.429232352 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Antigravity*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$ExpectedExitCodes = @(0, 3010)
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/box-drive/windows.json=== Install // 8959087b -> 653463f0 ===
--- /tmp/old.rjvKIE 2026-07-01 22:14:56.485233648 +0000
+++ /tmp/new.kQJv3U 2026-07-01 22:14:56.485233648 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Box"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/quiet"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/windows.json=== Install // 9a0c2b15 -> b2a17e99 ===
--- /tmp/old.QWVEM0 2026-07-01 22:14:56.535234804 +0000
+++ /tmp/new.9JRmVQ 2026-07-01 22:14:56.535234804 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Brave*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$exitCode = 0
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cursor/windows.json=== Install // 03589b5e -> 79e083fb ===
--- /tmp/old.KLn0Dw 2026-07-01 22:14:56.584235938 +0000
+++ /tmp/new.WRaMoA 2026-07-01 22:14:56.584235938 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Cursor*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dropbox/windows.json=== Install // 8959087b -> 65bc4efa ===
--- /tmp/old.bSpUW0 2026-07-01 22:14:56.631237025 +0000
+++ /tmp/new.WK34Xv 2026-07-01 22:14:56.632237048 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Dropbox*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox/windows.json=== Install // 80fb9175 -> d6bc46f1 ===
--- /tmp/old.8yjSpr 2026-07-01 22:14:56.679238135 +0000
+++ /tmp/new.T2hKWG 2026-07-01 22:14:56.679238135 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox (x64*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox@esr/windows.json=== Install // 36995f4f -> 389738de ===
--- /tmp/old.krxSBe 2026-07-01 22:14:56.735239431 +0000
+++ /tmp/new.HMCcxd 2026-07-01 22:14:56.735239431 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox ESR*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/gimp/windows.json=== Install // 72113c10 -> 3816f627 ===
--- /tmp/old.cOwkhI 2026-07-01 22:14:56.783240541 +0000
+++ /tmp/new.Qr7LDI 2026-07-01 22:14:56.783240541 +0000
@@ -1,48 +1,55 @@
-# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
-# GIMP also offers a per-user install, so a host may already have a stale per-user
-# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
-# + every loaded user hive), so a lingering per-user copy keeps the policy red and
-# leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GIMP 3*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
-# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
-$displayNameLike = "GIMP 3*"
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
- param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -50,55 +57,60 @@
$key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
if (-not $key.DisplayName) { continue }
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
- if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
- if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
- if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
- if (-not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
+# Learn more about .exe install scripts:
+# http://fleetdm.com/learn-more-about/exe-install-scripts
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/github-desktop/windows.json=== Install // 8959087b -> 9fe80ace ===
--- /tmp/old.00Fi86 2026-07-01 22:14:56.832241674 +0000
+++ /tmp/new.qFy9hM 2026-07-01 22:14:56.833241698 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GitHub Desktop*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "-s"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-chrome/windows.json=== Install // 8959087b -> 29e7984f ===
--- /tmp/old.mmI12U 2026-07-01 22:14:56.879242762 +0000
+++ /tmp/new.H941Fk 2026-07-01 22:14:56.879242762 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Chrome*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-drive/windows.json=== Install // fa36b892 -> 8e24f67d ===
--- /tmp/old.yNwn9y 2026-07-01 22:14:56.929243918 +0000
+++ /tmp/new.fPaw4F 2026-07-01 22:14:56.930243941 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Drive"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent --force_stop"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/kiro/windows.json=== Install // c5bc3c21 -> d7705b90 ===
--- /tmp/old.Q7aw1D 2026-07-01 22:14:56.985245214 +0000
+++ /tmp/new.EzMkix 2026-07-01 22:14:56.985245214 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Kiro*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-edge/windows.json=== Install // 8959087b -> e460919b ===
--- /tmp/old.HFABa4 2026-07-01 22:14:57.033246324 +0000
+++ /tmp/new.PUUIQb 2026-07-01 22:14:57.034246347 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Edge*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/mullvad-browser/windows.json=== Install // de703749 -> 00b6fdaa ===
--- /tmp/old.8YkJWL 2026-07-01 22:14:57.086247550 +0000
+++ /tmp/new.aLfE0M 2026-07-01 22:14:57.086247550 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Mullvad Browser*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install // ab0b56ab -> 991bcdbd ===
--- /tmp/old.ULGqAa 2026-07-01 22:14:57.139248776 +0000
+++ /tmp/new.I4jwGA 2026-07-01 22:14:57.139248776 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft OneDrive*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 733f8df6 -> 47adf288 ===
--- /tmp/old.HX3A0y 2026-07-01 22:14:57.188249909 +0000
+++ /tmp/new.A1vCT2 2026-07-01 22:14:57.188249909 +0000
@@ -1,50 +1,55 @@
-# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
-# ships a per-user installer (PowerToysUserSetup), so a host may already have a
-# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
-# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
-# the policy red and leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "PowerToys*"
+$fmaPublisherLike = "Microsoft Corporation*"
+$fmaFallbackUninstallArgs = "/uninstall /quiet /norestart"
-$ExpectedExitCodes = @(0, 1641, 3010, 1223)
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
+function Remove-FmaOtherScopeCopies {
param(
- [Parameter(Mandatory = $true)][string]$DisplayNameLike,
- [string]$PublisherLike = ''
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
)
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -54,63 +59,57 @@
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
- if ($exe -match '(?i)msiexec') {
- $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
- if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
- if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
- } else {
- if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
- if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
}
- if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
- Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
+$ExpectedExitCodes = @(0, 1641, 3010, 1223)
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/visual-studio-code/windows.json=== Install // 49122823 -> 2c16ed6d ===
--- /tmp/old.2GjGk1 2026-07-01 22:14:57.242251158 +0000
+++ /tmp/new.eKB30T 2026-07-01 22:14:57.242251158 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Visual Studio Code*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // d5d49757 -> d9389dbd ===
--- /tmp/old.Ayrkaq 2026-07-01 22:14:57.294252361 +0000
+++ /tmp/new.6LUNUo 2026-07-01 22:14:57.294252361 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Vivaldi*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Install Vivaldi silently, machine-wide (Chromium-based browser).
# Fleet runs installs as SYSTEM, so --system-level is required to install for
# all users under %ProgramFiles% (and register under HKLM). Without it the
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vscodium/windows.json=== Install // 38852240 -> 808c2033 ===
--- /tmp/old.ekacRe 2026-07-01 22:14:57.361253911 +0000
+++ /tmp/new.BKgM8w 2026-07-01 22:14:57.361253911 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "VSCodium*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windsurf/windows.json=== Install // 232abd06 -> 044c2bcd ===
--- /tmp/old.Vlq1sf 2026-07-01 22:14:57.429255484 +0000
+++ /tmp/new.OPSRzO 2026-07-01 22:14:57.429255484 +0000
@@ -1,3 +1,112 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Windsurf*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
# install switches:
# /VERYSILENT = no UI at all
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zoom/windows.json=== Install // 8959087b -> a6f3a656 ===
--- /tmp/old.HU0Mkx 2026-07-01 22:14:57.488256849 +0000
+++ /tmp/new.YM71G8 2026-07-01 22:14:57.488256849 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Zoom*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) === |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #48603 +/- ##
=======================================
Coverage 68.01% 68.01%
=======================================
Files 3678 3678
Lines 233758 233761 +3
Branches 12453 12453
=======================================
+ Hits 158981 158993 +12
+ Misses 60475 60472 -3
+ Partials 14302 14296 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Script Diff Resultsee/maintained-apps/outputs/1password/windows.json=== Install // 8959087b -> b5d4dd9b ===
--- /tmp/old.YbH9i1 2026-07-02 02:31:12.567301584 +0000
+++ /tmp/new.L06p4U 2026-07-02 02:31:12.567301584 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "1Password*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/affinity/windows.json=== Install // 9e9c1496 -> 5d093085 ===
--- /tmp/old.cpS5i7 2026-07-02 02:31:12.630301804 +0000
+++ /tmp/new.2jtdei 2026-07-02 02:31:12.631301807 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Affinity'; Publisher = 'Canva*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/antigravity-ide/windows.json=== Install // 8b15c4d0 -> c7f687a6 ===
--- /tmp/old.FgtdIe 2026-07-02 02:31:12.680301979 +0000
+++ /tmp/new.Ru2Gyk 2026-07-02 02:31:12.680301979 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Antigravity*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$ExpectedExitCodes = @(0, 3010)
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/arc/windows.json=== Install // d2d5c7dc -> 9b1928a1 ===
--- /tmp/old.FyVm6A 2026-07-02 02:31:12.728302146 +0000
+++ /tmp/new.nOemLJ 2026-07-02 02:31:12.729302150 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Arc'; Publisher = 'The Browser Company*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/box-drive/windows.json=== Install // 8959087b -> 653463f0 ===
--- /tmp/old.vju1O0 2026-07-02 02:31:12.783302338 +0000
+++ /tmp/new.xCu4QR 2026-07-02 02:31:12.784302341 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Box"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/quiet"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/windows.json=== Install // 9a0c2b15 -> b2a17e99 ===
--- /tmp/old.FgB8tK 2026-07-02 02:31:12.831302506 +0000
+++ /tmp/new.AUFY2D 2026-07-02 02:31:12.832302509 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Brave*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$exitCode = 0
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/claude/windows.json=== Install // 16c020cc -> 5b1e734d ===
--- /tmp/old.ts30A9 2026-07-02 02:31:12.880302676 +0000
+++ /tmp/new.4maJCx 2026-07-02 02:31:12.880302676 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Claude'; Publisher = 'Anthropic*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cursor/windows.json=== Install // 03589b5e -> 79e083fb ===
--- /tmp/old.NwCJp9 2026-07-02 02:31:12.930302851 +0000
+++ /tmp/new.MnJFv4 2026-07-02 02:31:12.930302851 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Cursor*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dropbox/windows.json=== Install // 8959087b -> 65bc4efa ===
--- /tmp/old.Jv3eTK 2026-07-02 02:31:12.976303012 +0000
+++ /tmp/new.yHtzXH 2026-07-02 02:31:12.976303012 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Dropbox*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox/windows.json=== Install // 80fb9175 -> d6bc46f1 ===
--- /tmp/old.Ku5VKl 2026-07-02 02:31:13.021303169 +0000
+++ /tmp/new.98sFaR 2026-07-02 02:31:13.021303169 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox (x64*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox@esr/windows.json=== Install // 36995f4f -> 389738de ===
--- /tmp/old.Ey5qPy 2026-07-02 02:31:13.073303350 +0000
+++ /tmp/new.NQGjmX 2026-07-02 02:31:13.073303350 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox ESR*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/gimp/windows.json=== Install // 72113c10 -> 3816f627 ===
--- /tmp/old.NFb4cz 2026-07-02 02:31:13.120303514 +0000
+++ /tmp/new.1JiGR1 2026-07-02 02:31:13.120303514 +0000
@@ -1,48 +1,55 @@
-# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
-# GIMP also offers a per-user install, so a host may already have a stale per-user
-# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
-# + every loaded user hive), so a lingering per-user copy keeps the policy red and
-# leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GIMP 3*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
-# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
-$displayNameLike = "GIMP 3*"
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
- param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -50,55 +57,60 @@
$key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
if (-not $key.DisplayName) { continue }
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
- if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
- if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
- if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
- if (-not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
+# Learn more about .exe install scripts:
+# http://fleetdm.com/learn-more-about/exe-install-scripts
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/github-desktop/windows.json=== Install // 8959087b -> 9fe80ace ===
--- /tmp/old.P7IbNO 2026-07-02 02:31:13.167303678 +0000
+++ /tmp/new.QFhB6d 2026-07-02 02:31:13.168303682 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GitHub Desktop*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "-s"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-chrome/windows.json=== Install // 8959087b -> 29e7984f ===
--- /tmp/old.K2UGqT 2026-07-02 02:31:13.213303839 +0000
+++ /tmp/new.onqoJW 2026-07-02 02:31:13.213303839 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Chrome*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-drive/windows.json=== Install // fa36b892 -> 8e24f67d ===
--- /tmp/old.7QUu0B 2026-07-02 02:31:13.262304010 +0000
+++ /tmp/new.lcHlT7 2026-07-02 02:31:13.262304010 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Drive"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent --force_stop"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/kiro/windows.json=== Install // c5bc3c21 -> d7705b90 ===
--- /tmp/old.IfQLBf 2026-07-02 02:31:13.315304195 +0000
+++ /tmp/new.mRuSVh 2026-07-02 02:31:13.316304198 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Kiro*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-edge/windows.json=== Install // 8959087b -> e460919b ===
--- /tmp/old.WiEUFr 2026-07-02 02:31:13.361304355 +0000
+++ /tmp/new.Ux1y9W 2026-07-02 02:31:13.362304359 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Edge*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-teams/windows.json=== Install // d1b37440 -> 83db6f82 ===
--- /tmp/old.WIT6ZC 2026-07-02 02:31:13.415304544 +0000
+++ /tmp/new.mdtpgp 2026-07-02 02:31:13.415304544 +0000
@@ -1,3 +1,150 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Microsoft Teams'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Microsoft Teams classic'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Teams Machine-Wide Installer'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/mullvad-browser/windows.json=== Install // de703749 -> 00b6fdaa ===
--- /tmp/old.cGxnk2 2026-07-02 02:31:13.469304732 +0000
+++ /tmp/new.JgCmq6 2026-07-02 02:31:13.469304732 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Mullvad Browser*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install // ab0b56ab -> 991bcdbd ===
--- /tmp/old.NO0O46 2026-07-02 02:31:13.523304921 +0000
+++ /tmp/new.qb1hIO 2026-07-02 02:31:13.524304924 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft OneDrive*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 733f8df6 -> 47adf288 ===
--- /tmp/old.9Ad3PD 2026-07-02 02:31:13.571305088 +0000
+++ /tmp/new.k3MUqC 2026-07-02 02:31:13.571305088 +0000
@@ -1,50 +1,55 @@
-# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
-# ships a per-user installer (PowerToysUserSetup), so a host may already have a
-# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
-# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
-# the policy red and leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "PowerToys*"
+$fmaPublisherLike = "Microsoft Corporation*"
+$fmaFallbackUninstallArgs = "/uninstall /quiet /norestart"
-$ExpectedExitCodes = @(0, 1641, 3010, 1223)
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
+function Remove-FmaOtherScopeCopies {
param(
- [Parameter(Mandatory = $true)][string]$DisplayNameLike,
- [string]$PublisherLike = ''
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
)
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -54,63 +59,57 @@
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
- if ($exe -match '(?i)msiexec') {
- $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
- if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
- if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
- } else {
- if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
- if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
}
- if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
- Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
+$ExpectedExitCodes = @(0, 1641, 3010, 1223)
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/slack/windows.json=== Install // 8b41f934 -> b6e159c4 ===
--- /tmp/old.FFyLm2 2026-07-02 02:31:13.625305276 +0000
+++ /tmp/new.sVveSU 2026-07-02 02:31:13.625305276 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Slack'; Publisher = 'Slack Technologies*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/visual-studio-code/windows.json=== Install // 49122823 -> 2c16ed6d ===
--- /tmp/old.7SF3kh 2026-07-02 02:31:13.675305451 +0000
+++ /tmp/new.VOIdY1 2026-07-02 02:31:13.675305451 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Visual Studio Code*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // d5d49757 -> d9389dbd ===
--- /tmp/old.iatek7 2026-07-02 02:31:13.725305626 +0000
+++ /tmp/new.ViYVWA 2026-07-02 02:31:13.725305626 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Vivaldi*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Install Vivaldi silently, machine-wide (Chromium-based browser).
# Fleet runs installs as SYSTEM, so --system-level is required to install for
# all users under %ProgramFiles% (and register under HKLM). Without it the
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vscodium/windows.json=== Install // 38852240 -> 808c2033 ===
--- /tmp/old.e3054B 2026-07-02 02:31:13.790305852 +0000
+++ /tmp/new.tZEtln 2026-07-02 02:31:13.791305856 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "VSCodium*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windows-app/windows.json=== Install // f3fab53b -> 86e2a0dd ===
--- /tmp/old.GJq6pm 2026-07-02 02:31:13.846306048 +0000
+++ /tmp/new.83t0ip 2026-07-02 02:31:13.847306051 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Windows App'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windsurf/windows.json=== Install // 232abd06 -> 044c2bcd ===
--- /tmp/old.rLwLz7 2026-07-02 02:31:13.913306282 +0000
+++ /tmp/new.nM27Wr 2026-07-02 02:31:13.913306282 +0000
@@ -1,3 +1,112 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Windsurf*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
# install switches:
# /VERYSILENT = no UI at all
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zoom/windows.json=== Install // 8959087b -> a6f3a656 ===
--- /tmp/old.wOPLlO 2026-07-02 02:31:13.965306463 +0000
+++ /tmp/new.mHKlGq 2026-07-02 02:31:13.965306463 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Zoom*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) === |
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The validator's broad LIKE '%Microsoft Edge%' search also matches the preinstalled 'Microsoft Edge WebView2 Runtime', which Microsoft version-locks to Edge releases. When the runner image's WebView2 version coincides with the manifest version, the post-uninstall check falsely reported Edge as still installed (seen on run 28561163533: WebView2 149.0.4022.98 == Edge target after the image updated from .80).
Script Diff Resultsee/maintained-apps/outputs/1password/windows.json=== Install // 8959087b -> b5d4dd9b ===
--- /tmp/old.k6EAJV 2026-07-02 03:02:29.408720912 +0000
+++ /tmp/new.pTIx0t 2026-07-02 03:02:29.409720913 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "1Password*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/affinity/windows.json=== Install // 9e9c1496 -> 5d093085 ===
--- /tmp/old.RAoz4j 2026-07-02 03:02:29.469720934 +0000
+++ /tmp/new.ybqzdu 2026-07-02 03:02:29.470720934 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Affinity'; Publisher = 'Canva*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/antigravity-ide/windows.json=== Install // 8b15c4d0 -> c7f687a6 ===
--- /tmp/old.3oSOCd 2026-07-02 03:02:29.528720955 +0000
+++ /tmp/new.7AuaS4 2026-07-02 03:02:29.528720955 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Antigravity*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$ExpectedExitCodes = @(0, 3010)
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/arc/windows.json=== Install // d2d5c7dc -> 9b1928a1 ===
--- /tmp/old.4BJXPu 2026-07-02 03:02:29.583720974 +0000
+++ /tmp/new.Kvqoww 2026-07-02 03:02:29.584720975 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Arc'; Publisher = 'The Browser Company*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/box-drive/windows.json=== Install // 8959087b -> 653463f0 ===
--- /tmp/old.Qk4PwD 2026-07-02 03:02:29.648720998 +0000
+++ /tmp/new.VGFtKb 2026-07-02 03:02:29.648720998 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Box"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/quiet"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/windows.json=== Install // 9a0c2b15 -> b2a17e99 ===
--- /tmp/old.WnPO6s 2026-07-02 03:02:29.704721017 +0000
+++ /tmp/new.NMvWoU 2026-07-02 03:02:29.704721017 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Brave*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$exitCode = 0
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/claude/windows.json=== Install // 16c020cc -> 5b1e734d ===
--- /tmp/old.8shANC 2026-07-02 03:02:29.760721037 +0000
+++ /tmp/new.Wrd1dE 2026-07-02 03:02:29.761721038 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Claude'; Publisher = 'Anthropic*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cursor/windows.json=== Install // 03589b5e -> 79e083fb ===
--- /tmp/old.cjbvdO 2026-07-02 03:02:29.817721058 +0000
+++ /tmp/new.Xn6UMw 2026-07-02 03:02:29.817721058 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Cursor*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dropbox/windows.json=== Install // 8959087b -> 65bc4efa ===
--- /tmp/old.lelvi6 2026-07-02 03:02:29.871721077 +0000
+++ /tmp/new.tWKBWx 2026-07-02 03:02:29.872721077 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Dropbox*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox/windows.json=== Install // 80fb9175 -> d6bc46f1 ===
--- /tmp/old.ecS4k9 2026-07-02 03:02:29.926721096 +0000
+++ /tmp/new.Ys8EMi 2026-07-02 03:02:29.927721097 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox (x64*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox@esr/windows.json=== Install // 36995f4f -> 389738de ===
--- /tmp/old.Khj1mq 2026-07-02 03:02:29.988721118 +0000
+++ /tmp/new.9M3MTe 2026-07-02 03:02:29.988721118 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox ESR*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/gimp/windows.json=== Install // 72113c10 -> 3816f627 ===
--- /tmp/old.61m8Fl 2026-07-02 03:02:30.042721137 +0000
+++ /tmp/new.M1qc3R 2026-07-02 03:02:30.042721137 +0000
@@ -1,48 +1,55 @@
-# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
-# GIMP also offers a per-user install, so a host may already have a stale per-user
-# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
-# + every loaded user hive), so a lingering per-user copy keeps the policy red and
-# leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GIMP 3*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
-# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
-$displayNameLike = "GIMP 3*"
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
- param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -50,55 +57,60 @@
$key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
if (-not $key.DisplayName) { continue }
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
- if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
- if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
- if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
- if (-not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
+# Learn more about .exe install scripts:
+# http://fleetdm.com/learn-more-about/exe-install-scripts
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/github-desktop/windows.json=== Install // 8959087b -> 9fe80ace ===
--- /tmp/old.lUvnrE 2026-07-02 03:02:30.097721157 +0000
+++ /tmp/new.BsRgxb 2026-07-02 03:02:30.097721157 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GitHub Desktop*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "-s"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-chrome/windows.json=== Install // 8959087b -> 29e7984f ===
--- /tmp/old.DGjLTt 2026-07-02 03:02:30.151721176 +0000
+++ /tmp/new.ce9MdR 2026-07-02 03:02:30.152721176 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Chrome*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-drive/windows.json=== Install // fa36b892 -> 8e24f67d ===
--- /tmp/old.slQQVG 2026-07-02 03:02:30.207721196 +0000
+++ /tmp/new.gtMyVJ 2026-07-02 03:02:30.208721196 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Drive"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent --force_stop"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/kiro/windows.json=== Install // c5bc3c21 -> d7705b90 ===
--- /tmp/old.e3i87a 2026-07-02 03:02:30.275721220 +0000
+++ /tmp/new.QiN8Zr 2026-07-02 03:02:30.276721220 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Kiro*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-edge/windows.json=== Install // 8959087b -> e460919b ===
--- /tmp/old.n9TIK4 2026-07-02 03:02:30.332721240 +0000
+++ /tmp/new.N5oUWi 2026-07-02 03:02:30.333721241 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Edge*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-teams/windows.json=== Install // d1b37440 -> 83db6f82 ===
--- /tmp/old.QDBbqC 2026-07-02 03:02:30.392721261 +0000
+++ /tmp/new.NOmhks 2026-07-02 03:02:30.392721261 +0000
@@ -1,3 +1,150 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Microsoft Teams'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Microsoft Teams classic'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Teams Machine-Wide Installer'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/mullvad-browser/windows.json=== Install // de703749 -> 00b6fdaa ===
--- /tmp/old.bZxnI8 2026-07-02 03:02:30.455721284 +0000
+++ /tmp/new.M8xw10 2026-07-02 03:02:30.455721284 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Mullvad Browser*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install // ab0b56ab -> 991bcdbd ===
--- /tmp/old.XAsMkj 2026-07-02 03:02:30.515721305 +0000
+++ /tmp/new.vrAzRi 2026-07-02 03:02:30.516721305 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft OneDrive*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 733f8df6 -> 47adf288 ===
--- /tmp/old.qJcVPl 2026-07-02 03:02:30.568721324 +0000
+++ /tmp/new.0Tgyz4 2026-07-02 03:02:30.569721324 +0000
@@ -1,50 +1,55 @@
-# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
-# ships a per-user installer (PowerToysUserSetup), so a host may already have a
-# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
-# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
-# the policy red and leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "PowerToys*"
+$fmaPublisherLike = "Microsoft Corporation*"
+$fmaFallbackUninstallArgs = "/uninstall /quiet /norestart"
-$ExpectedExitCodes = @(0, 1641, 3010, 1223)
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
+function Remove-FmaOtherScopeCopies {
param(
- [Parameter(Mandatory = $true)][string]$DisplayNameLike,
- [string]$PublisherLike = ''
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
)
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -54,63 +59,57 @@
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
- if ($exe -match '(?i)msiexec') {
- $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
- if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
- if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
- } else {
- if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
- if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
}
- if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
- Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
+$ExpectedExitCodes = @(0, 1641, 3010, 1223)
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/slack/windows.json=== Install // 8b41f934 -> b6e159c4 ===
--- /tmp/old.A4BBTU 2026-07-02 03:02:30.629721346 +0000
+++ /tmp/new.KbVi09 2026-07-02 03:02:30.629721346 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Slack'; Publisher = 'Slack Technologies*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/visual-studio-code/windows.json=== Install // 49122823 -> 2c16ed6d ===
--- /tmp/old.uHARvh 2026-07-02 03:02:30.685721365 +0000
+++ /tmp/new.I2qPvM 2026-07-02 03:02:30.686721366 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Visual Studio Code*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // d5d49757 -> d9389dbd ===
--- /tmp/old.EUkOMp 2026-07-02 03:02:30.742721386 +0000
+++ /tmp/new.JH77xl 2026-07-02 03:02:30.742721386 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Vivaldi*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Install Vivaldi silently, machine-wide (Chromium-based browser).
# Fleet runs installs as SYSTEM, so --system-level is required to install for
# all users under %ProgramFiles% (and register under HKLM). Without it the
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vscodium/windows.json=== Install // 38852240 -> 808c2033 ===
--- /tmp/old.ec9RMN 2026-07-02 03:02:30.816721412 +0000
+++ /tmp/new.Doxps3 2026-07-02 03:02:30.817721412 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "VSCodium*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windows-app/windows.json=== Install // f3fab53b -> 86e2a0dd ===
--- /tmp/old.R7QOwF 2026-07-02 03:02:30.881721435 +0000
+++ /tmp/new.zZcQ5H 2026-07-02 03:02:30.882721435 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Windows App'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windsurf/windows.json=== Install // 232abd06 -> 044c2bcd ===
--- /tmp/old.IlrhHf 2026-07-02 03:02:30.960721463 +0000
+++ /tmp/new.UZN5uH 2026-07-02 03:02:30.960721463 +0000
@@ -1,3 +1,112 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Windsurf*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
# install switches:
# /VERYSILENT = no UI at all
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zoom/windows.json=== Install // 8959087b -> a6f3a656 ===
--- /tmp/old.LfCyDk 2026-07-02 03:02:31.019721484 +0000
+++ /tmp/new.R3m9cY 2026-07-02 03:02:31.019721484 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Zoom*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) === |
The poll exited as soon as firefox.exe appeared on disk, but the NSIS installer writes the registry uninstall entry (what detection reads) last — so osquery could query programs before the entry existed (seen on run 28562265782: version 140.12.0 not found 6s after script start while the installer child was still running). Now waits for both the exe and the Mozilla Firefox*ESR* uninstall entry.
Script Diff Resultsee/maintained-apps/outputs/1password/windows.json=== Install // 8959087b -> b5d4dd9b ===
--- /tmp/old.gUvD5s 2026-07-02 03:34:21.630071454 +0000
+++ /tmp/new.CQ0L5s 2026-07-02 03:34:21.630071454 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "1Password*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/affinity/windows.json=== Install // 9e9c1496 -> 5d093085 ===
--- /tmp/old.gGtGPR 2026-07-02 03:34:21.684071094 +0000
+++ /tmp/new.8Ii62z 2026-07-02 03:34:21.684071094 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Affinity'; Publisher = 'Canva*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/antigravity-ide/windows.json=== Install // 8b15c4d0 -> c7f687a6 ===
--- /tmp/old.YyTuD8 2026-07-02 03:34:21.733070755 +0000
+++ /tmp/new.nKz0Wn 2026-07-02 03:34:21.734070747 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Antigravity*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$ExpectedExitCodes = @(0, 3010)
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/arc/windows.json=== Install // d2d5c7dc -> 9b1928a1 ===
--- /tmp/old.LC3t2S 2026-07-02 03:34:21.781070392 +0000
+++ /tmp/new.LpYMKI 2026-07-02 03:34:21.781070392 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Arc'; Publisher = 'The Browser Company*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/box-drive/windows.json=== Install // 8959087b -> 653463f0 ===
--- /tmp/old.Jp3wtS 2026-07-02 03:34:21.836069977 +0000
+++ /tmp/new.H6oxhm 2026-07-02 03:34:21.837069969 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Box"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/quiet"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/windows.json=== Install // 9a0c2b15 -> b2a17e99 ===
--- /tmp/old.Fs8cPX 2026-07-02 03:34:21.892069554 +0000
+++ /tmp/new.3JLtrO 2026-07-02 03:34:21.893069547 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Brave*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$exitCode = 0
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/claude/windows.json=== Install // 16c020cc -> 5b1e734d ===
--- /tmp/old.tGyeJy 2026-07-02 03:34:21.941069184 +0000
+++ /tmp/new.7ZjY5K 2026-07-02 03:34:21.942069177 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Claude'; Publisher = 'Anthropic*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cursor/windows.json=== Install // 03589b5e -> 79e083fb ===
--- /tmp/old.ourDma 2026-07-02 03:34:21.990068814 +0000
+++ /tmp/new.YPqWrr 2026-07-02 03:34:21.990068814 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Cursor*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dropbox/windows.json=== Install // 8959087b -> 65bc4efa ===
--- /tmp/old.SYomdF 2026-07-02 03:34:22.038068452 +0000
+++ /tmp/new.V58UFd 2026-07-02 03:34:22.038068452 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Dropbox*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox/windows.json=== Install // 80fb9175 -> d6bc46f1 ===
--- /tmp/old.LBYIkK 2026-07-02 03:34:22.083068112 +0000
+++ /tmp/new.txfa4a 2026-07-02 03:34:22.083068112 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox (x64*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox@esr/windows.json=== Install // 389738de -> 04e4b74e ===
--- /tmp/old.ZHlGI6 2026-07-02 03:34:22.130067757 +0000
+++ /tmp/new.G7wQUN 2026-07-02 03:34:22.130067757 +0000
@@ -121,12 +121,25 @@
# browser after installing and blocks until it is closed.
Start-Process -FilePath "$exeFilePath" -ArgumentList "/S"
-# Poll for installation to complete
+# Poll for installation to complete. firefox.exe lands on disk before the
+# installer finishes, and the registry uninstall entry -- what Fleet's
+# detection (osquery "programs") reads -- is written last, so wait for both.
+# Exiting on the file alone races detection: the policy/validator can query
+# "programs" before the entry exists.
+$uninstallRoots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+)
$elapsed = 0
while ($elapsed -lt $maxWaitSeconds) {
Start-Sleep -Seconds 5
$elapsed += 5
- if (Test-Path "$installDir\firefox.exe") {
+ if (-not (Test-Path "$installDir\firefox.exe")) { continue }
+ $entry = Get-ChildItem -Path $uninstallRoots -ErrorAction SilentlyContinue |
+ ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
+ Where-Object { $_.DisplayName -like 'Mozilla Firefox*ESR*' } |
+ Select-Object -First 1
+ if ($entry) {
Write-Host "Firefox ESR installed successfully after $elapsed seconds"
Exit 0
}
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/gimp/windows.json=== Install // 72113c10 -> 3816f627 ===
--- /tmp/old.5EFEH1 2026-07-02 03:34:22.176067409 +0000
+++ /tmp/new.uRI3K6 2026-07-02 03:34:22.176067409 +0000
@@ -1,48 +1,55 @@
-# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
-# GIMP also offers a per-user install, so a host may already have a stale per-user
-# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
-# + every loaded user hive), so a lingering per-user copy keeps the policy red and
-# leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GIMP 3*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
-# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
-$displayNameLike = "GIMP 3*"
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
- param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -50,55 +57,60 @@
$key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
if (-not $key.DisplayName) { continue }
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
- if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
- if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
- if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
- if (-not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
+# Learn more about .exe install scripts:
+# http://fleetdm.com/learn-more-about/exe-install-scripts
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/github-desktop/windows.json=== Install // 8959087b -> 9fe80ace ===
--- /tmp/old.kHSu4y 2026-07-02 03:34:22.223067055 +0000
+++ /tmp/new.90HLAO 2026-07-02 03:34:22.223067055 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GitHub Desktop*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "-s"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-chrome/windows.json=== Install // 8959087b -> 29e7984f ===
--- /tmp/old.VRqPQq 2026-07-02 03:34:22.268066715 +0000
+++ /tmp/new.mo9OQH 2026-07-02 03:34:22.268066715 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Chrome*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-drive/windows.json=== Install // fa36b892 -> 8e24f67d ===
--- /tmp/old.q4STIl 2026-07-02 03:34:22.317066345 +0000
+++ /tmp/new.Ah86xL 2026-07-02 03:34:22.317066345 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Drive"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent --force_stop"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/kiro/windows.json=== Install // c5bc3c21 -> d7705b90 ===
--- /tmp/old.B0hHJK 2026-07-02 03:34:22.368065960 +0000
+++ /tmp/new.v0vM5v 2026-07-02 03:34:22.369065952 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Kiro*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-edge/windows.json=== Install // 8959087b -> e460919b ===
--- /tmp/old.imaVv6 2026-07-02 03:34:22.415065605 +0000
+++ /tmp/new.AmQifs 2026-07-02 03:34:22.415065605 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Edge*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-teams/windows.json=== Install // d1b37440 -> 83db6f82 ===
--- /tmp/old.wDC8Yp 2026-07-02 03:34:22.468065205 +0000
+++ /tmp/new.aCtju9 2026-07-02 03:34:22.469065197 +0000
@@ -1,3 +1,150 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Microsoft Teams'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Microsoft Teams classic'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Teams Machine-Wide Installer'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/mullvad-browser/windows.json=== Install // de703749 -> 00b6fdaa ===
--- /tmp/old.UMoSWg 2026-07-02 03:34:22.521064805 +0000
+++ /tmp/new.ItfXrC 2026-07-02 03:34:22.521064805 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Mullvad Browser*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install // ab0b56ab -> 991bcdbd ===
--- /tmp/old.KfCIux 2026-07-02 03:34:22.573064412 +0000
+++ /tmp/new.5AhDCd 2026-07-02 03:34:22.573064412 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft OneDrive*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 733f8df6 -> 47adf288 ===
--- /tmp/old.1V3UsR 2026-07-02 03:34:22.620064057 +0000
+++ /tmp/new.Lcgbvi 2026-07-02 03:34:22.620064057 +0000
@@ -1,50 +1,55 @@
-# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
-# ships a per-user installer (PowerToysUserSetup), so a host may already have a
-# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
-# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
-# the policy red and leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "PowerToys*"
+$fmaPublisherLike = "Microsoft Corporation*"
+$fmaFallbackUninstallArgs = "/uninstall /quiet /norestart"
-$ExpectedExitCodes = @(0, 1641, 3010, 1223)
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
+function Remove-FmaOtherScopeCopies {
param(
- [Parameter(Mandatory = $true)][string]$DisplayNameLike,
- [string]$PublisherLike = ''
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
)
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -54,63 +59,57 @@
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
- if ($exe -match '(?i)msiexec') {
- $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
- if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
- if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
- } else {
- if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
- if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
}
- if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
- Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
+$ExpectedExitCodes = @(0, 1641, 3010, 1223)
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/slack/windows.json=== Install // 8b41f934 -> b6e159c4 ===
--- /tmp/old.UlY90n 2026-07-02 03:34:22.675063642 +0000
+++ /tmp/new.YnFZ7c 2026-07-02 03:34:22.676063634 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Slack'; Publisher = 'Slack Technologies*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/visual-studio-code/windows.json=== Install // 49122823 -> 2c16ed6d ===
--- /tmp/old.QeUtWh 2026-07-02 03:34:22.728063241 +0000
+++ /tmp/new.vWNcY1 2026-07-02 03:34:22.729063234 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Visual Studio Code*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // d5d49757 -> d9389dbd ===
--- /tmp/old.wTc10h 2026-07-02 03:34:22.779062856 +0000
+++ /tmp/new.wSQHyf 2026-07-02 03:34:22.779062856 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Vivaldi*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Install Vivaldi silently, machine-wide (Chromium-based browser).
# Fleet runs installs as SYSTEM, so --system-level is required to install for
# all users under %ProgramFiles% (and register under HKLM). Without it the
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vscodium/windows.json=== Install // 38852240 -> 808c2033 ===
--- /tmp/old.0SkTXW 2026-07-02 03:34:22.844062366 +0000
+++ /tmp/new.7xn2zj 2026-07-02 03:34:22.845062358 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "VSCodium*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windows-app/windows.json=== Install // f3fab53b -> 86e2a0dd ===
--- /tmp/old.iwXlpt 2026-07-02 03:34:22.901061935 +0000
+++ /tmp/new.QvXTu1 2026-07-02 03:34:22.901061935 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Windows App'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windsurf/windows.json=== Install // 232abd06 -> 044c2bcd ===
--- /tmp/old.2mHVYd 2026-07-02 03:34:22.967061437 +0000
+++ /tmp/new.Tcq7Zh 2026-07-02 03:34:22.967061437 +0000
@@ -1,3 +1,112 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Windsurf*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
# install switches:
# /VERYSILENT = no UI at all
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zoom/windows.json=== Install // 8959087b -> a6f3a656 ===
--- /tmp/old.q2O2FD 2026-07-02 03:34:23.019061044 +0000
+++ /tmp/new.ptu3Nf 2026-07-02 03:34:23.019061044 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Zoom*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) === |
The completion poll accepted the OneDrive.exe binary OR the registry uninstall entry, but the binary lands before OneDriveSetup's child registers the ARP entry that detection reads — so the script could exit while registration was still pending (seen on run 28563420399). Success now requires the registry entry (native or WOW6432Node hive, with a DisplayName fallback), polling to the deadline even after the setup process exits.
Script Diff Resultsee/maintained-apps/outputs/1password/windows.json=== Install // 8959087b -> b5d4dd9b ===
--- /tmp/old.Zgq39I 2026-07-02 04:06:42.432743677 +0000
+++ /tmp/new.ZNMUvq 2026-07-02 04:06:42.433743688 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "1Password*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/affinity/windows.json=== Install // 9e9c1496 -> 5d093085 ===
--- /tmp/old.30WwUx 2026-07-02 04:06:42.487744308 +0000
+++ /tmp/new.t8zQtS 2026-07-02 04:06:42.487744308 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Affinity'; Publisher = 'Canva*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/antigravity-ide/windows.json=== Install // 8b15c4d0 -> c7f687a6 ===
--- /tmp/old.5X9hRT 2026-07-02 04:06:42.537744882 +0000
+++ /tmp/new.OFqcgr 2026-07-02 04:06:42.537744882 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Antigravity*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$ExpectedExitCodes = @(0, 3010)
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/arc/windows.json=== Install // d2d5c7dc -> 9b1928a1 ===
--- /tmp/old.DAsevl 2026-07-02 04:06:42.588745467 +0000
+++ /tmp/new.Cy9QsN 2026-07-02 04:06:42.588745467 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Arc'; Publisher = 'The Browser Company*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/box-drive/windows.json=== Install // 8959087b -> 653463f0 ===
--- /tmp/old.mfPI5l 2026-07-02 04:06:42.644746110 +0000
+++ /tmp/new.YTTlFX 2026-07-02 04:06:42.644746110 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Box"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/quiet"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/windows.json=== Install // 9a0c2b15 -> b2a17e99 ===
--- /tmp/old.iuYkpx 2026-07-02 04:06:42.693746673 +0000
+++ /tmp/new.8yLHFv 2026-07-02 04:06:42.694746684 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Brave*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
$exitCode = 0
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/claude/windows.json=== Install // 16c020cc -> 5b1e734d ===
--- /tmp/old.H8FV1O 2026-07-02 04:06:42.744747258 +0000
+++ /tmp/new.HAJDKL 2026-07-02 04:06:42.744747258 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Claude'; Publisher = 'Anthropic*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cursor/windows.json=== Install // 03589b5e -> 79e083fb ===
--- /tmp/old.dHFvVN 2026-07-02 04:06:42.797747866 +0000
+++ /tmp/new.AH9xUY 2026-07-02 04:06:42.797747866 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Cursor*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dropbox/windows.json=== Install // 8959087b -> 65bc4efa ===
--- /tmp/old.zJ8Ykq 2026-07-02 04:06:42.845748417 +0000
+++ /tmp/new.uXnMKS 2026-07-02 04:06:42.845748417 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Dropbox*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox/windows.json=== Install // 80fb9175 -> d6bc46f1 ===
--- /tmp/old.7GG5di 2026-07-02 04:06:42.893748969 +0000
+++ /tmp/new.YaMogS 2026-07-02 04:06:42.894748980 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Mozilla Firefox (x64*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/firefox@esr/windows.json=== Install // 389738de -> 04e4b74e ===
--- /tmp/old.TQQsLP 2026-07-02 04:06:42.944749554 +0000
+++ /tmp/new.GF2Go2 2026-07-02 04:06:42.944749554 +0000
@@ -121,12 +121,25 @@
# browser after installing and blocks until it is closed.
Start-Process -FilePath "$exeFilePath" -ArgumentList "/S"
-# Poll for installation to complete
+# Poll for installation to complete. firefox.exe lands on disk before the
+# installer finishes, and the registry uninstall entry -- what Fleet's
+# detection (osquery "programs") reads -- is written last, so wait for both.
+# Exiting on the file alone races detection: the policy/validator can query
+# "programs" before the entry exists.
+$uninstallRoots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+)
$elapsed = 0
while ($elapsed -lt $maxWaitSeconds) {
Start-Sleep -Seconds 5
$elapsed += 5
- if (Test-Path "$installDir\firefox.exe") {
+ if (-not (Test-Path "$installDir\firefox.exe")) { continue }
+ $entry = Get-ChildItem -Path $uninstallRoots -ErrorAction SilentlyContinue |
+ ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
+ Where-Object { $_.DisplayName -like 'Mozilla Firefox*ESR*' } |
+ Select-Object -First 1
+ if ($entry) {
Write-Host "Firefox ESR installed successfully after $elapsed seconds"
Exit 0
}
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/gimp/windows.json=== Install // 72113c10 -> 3816f627 ===
--- /tmp/old.dyDswu 2026-07-02 04:06:42.994750128 +0000
+++ /tmp/new.2WLzXO 2026-07-02 04:06:42.995750140 +0000
@@ -1,48 +1,55 @@
-# GIMP is managed by Fleet as a PER-MACHINE install (Inno Setup /ALLUSERS).
-# GIMP also offers a per-user install, so a host may already have a stale per-user
-# copy. Fleet's patch policy is scope-blind (osquery's "programs" table reads HKLM
-# + every loaded user hive), so a lingering per-user copy keeps the policy red and
-# leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GIMP 3*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
-# Match GIMP 3.x only (the FMA targets GIMP.GIMP.3); avoids touching GIMP 2.
-$displayNameLike = "GIMP 3*"
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
- param([Parameter(Mandatory = $true)][string]$DisplayNameLike)
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -50,55 +57,60 @@
$key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
if (-not $key.DisplayName) { continue }
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # GIMP uses an Inno Setup uninstaller; ensure a silent uninstall.
- if ($uninstallArgs -notmatch '(?i)/VERYSILENT') { $uninstallArgs = ("$uninstallArgs /VERYSILENT").Trim() }
- if ($uninstallArgs -notmatch '(?i)/SUPPRESSMSGBOXES') { $uninstallArgs = ("$uninstallArgs /SUPPRESSMSGBOXES").Trim() }
- if ($uninstallArgs -notmatch '(?i)/NORESTART') { $uninstallArgs = ("$uninstallArgs /NORESTART").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
- if (-not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Remove-OtherScopeCopies -DisplayNameLike $displayNameLike
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
+# Learn more about .exe install scripts:
+# http://fleetdm.com/learn-more-about/exe-install-scripts
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/github-desktop/windows.json=== Install // 8959087b -> 9fe80ace ===
--- /tmp/old.XrXoRQ 2026-07-02 04:06:43.049750760 +0000
+++ /tmp/new.QckKgq 2026-07-02 04:06:43.049750760 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "GitHub Desktop*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "-s"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-chrome/windows.json=== Install // 8959087b -> 29e7984f ===
--- /tmp/old.kDBnA1 2026-07-02 04:06:43.105751402 +0000
+++ /tmp/new.UXDzv6 2026-07-02 04:06:43.106751414 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Chrome*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-drive/windows.json=== Install // fa36b892 -> 8e24f67d ===
--- /tmp/old.7VrXsW 2026-07-02 04:06:43.156751988 +0000
+++ /tmp/new.gZY9Wy 2026-07-02 04:06:43.156751988 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Google Drive"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--silent --force_stop"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/kiro/windows.json=== Install // c5bc3c21 -> d7705b90 ===
--- /tmp/old.w1n872 2026-07-02 04:06:43.208752585 +0000
+++ /tmp/new.ugwQ4L 2026-07-02 04:06:43.209752597 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Kiro*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-edge/windows.json=== Install // 8959087b -> e460919b ===
--- /tmp/old.ehwWcM 2026-07-02 04:06:43.257753147 +0000
+++ /tmp/new.EAUPdY 2026-07-02 04:06:43.257753147 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Edge*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/microsoft-teams/windows.json=== Install // d1b37440 -> 83db6f82 ===
--- /tmp/old.ZgJ8ni 2026-07-02 04:06:43.312753779 +0000
+++ /tmp/new.nurQCD 2026-07-02 04:06:43.312753779 +0000
@@ -1,3 +1,150 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Microsoft Teams'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Microsoft Teams classic'; Publisher = 'Microsoft*'; FallbackArgs = '--uninstall -s' }
+ @{ Name = 'Teams Machine-Wide Installer'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/mullvad-browser/windows.json=== Install // de703749 -> 00b6fdaa ===
--- /tmp/old.82edPJ 2026-07-02 04:06:43.366754399 +0000
+++ /tmp/new.X8lvdq 2026-07-02 04:06:43.367754410 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "user" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "user"
+$fmaDisplayNameLike = "Mullvad Browser*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/S"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
#
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install // 991bcdbd -> fb13c961 ===
--- /tmp/old.cCw3wC 2026-07-02 04:06:43.414754950 +0000
+++ /tmp/new.rsoV8B 2026-07-02 04:06:43.414754950 +0000
@@ -120,48 +120,63 @@
# silentinstallhq.com). The catch: OneDriveSetup.exe spawns several child
# processes and starts the resident OneDrive.exe, so a plain Start-Process -Wait
# can wait indefinitely and hit the CI step timeout. Instead, start the
-# installer, then poll for the per-machine install to land (registry uninstall
-# key + the all-users binary) and return success as soon as it appears.
+# installer, then poll for the per-machine install's registry uninstall entry
+# and return success once it is registered.
$process = Start-Process -FilePath "$exeFilePath" -ArgumentList "/allusers /silent" -PassThru
-# Per-machine OneDrive registers an uninstall key and drops OneDrive.exe under
-# Program Files (x86) (or Program Files on x86 OS).
-$uninstallKey = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\OneDriveSetup.exe"
+# Per-machine OneDrive drops OneDrive.exe under Program Files and registers an
+# uninstall entry -- which is what Fleet's detection (osquery "programs") reads.
+# The binary lands BEFORE the entry is registered, so success requires the
+# registry entry; exiting on the binary alone races detection. Modern x64
+# builds register under the native hive, older ones under WOW6432Node.
+# OneDriveSetup.exe may also exit while a child process finishes the
+# registration, so keep polling until the deadline even after it exits.
+$uninstallKeys = @(
+ "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\OneDriveSetup.exe",
+ "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\OneDriveSetup.exe"
+)
+$uninstallRoots = @(
+ "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
+ "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+)
$exePaths = @(
"$env:ProgramFiles\Microsoft OneDrive\OneDrive.exe",
"${env:ProgramFiles(x86)}\Microsoft OneDrive\OneDrive.exe"
)
+function Test-OneDriveRegistered {
+ foreach ($k in $uninstallKeys) {
+ if (Test-Path $k) { return $true }
+ }
+ # Fallback in case the key name drifts across OneDrive builds.
+ $entry = Get-ChildItem -Path $uninstallRoots -ErrorAction SilentlyContinue |
+ ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue } |
+ Where-Object { $_.DisplayName -eq 'Microsoft OneDrive' } |
+ Select-Object -First 1
+ return [bool]$entry
+}
+
$timeoutSeconds = 240
$deadline = (Get-Date).AddSeconds($timeoutSeconds)
-$installed = $false
+$registered = $false
while ((Get-Date) -lt $deadline) {
- $exeExists = $false
- foreach ($p in $exePaths) {
- if ($p -and (Test-Path $p)) { $exeExists = $true; break }
- }
- if ((Test-Path $uninstallKey) -or $exeExists) {
- $installed = $true
- break
- }
- # If the top-level setup process exited, capture its code and stop polling.
- if ($process.HasExited) { break }
+ if (Test-OneDriveRegistered) { $registered = $true; break }
Start-Sleep -Seconds 5
}
-# Final check in case the setup process exited right before the loop bailed.
-if (-not $installed) {
- $exeExists = $false
- foreach ($p in $exePaths) {
- if ($p -and (Test-Path $p)) { $exeExists = $true; break }
- }
- if ((Test-Path $uninstallKey) -or $exeExists) { $installed = $true }
+if ($registered) {
+ Write-Host "OneDrive per-machine install registered."
+ Exit 0
}
-if ($installed) {
- Write-Host "OneDrive per-machine install detected."
+$exeExists = $false
+foreach ($p in $exePaths) {
+ if ($p -and (Test-Path $p)) { $exeExists = $true; break }
+}
+if ($exeExists) {
+ Write-Host "Warning: OneDrive.exe present but uninstall entry not registered within $timeoutSeconds seconds."
Exit 0
}
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/powertoys/windows.json=== Install // 733f8df6 -> 47adf288 ===
--- /tmp/old.NKlGjh 2026-07-02 04:06:43.463755512 +0000
+++ /tmp/new.h4BKmy 2026-07-02 04:06:43.464755524 +0000
@@ -1,50 +1,55 @@
-# PowerToys is managed by Fleet as a PER-MACHINE (HKLM) install. Windows also
-# ships a per-user installer (PowerToysUserSetup), so a host may already have a
-# stale per-user copy. Fleet's patch policy is scope-blind (osquery's "programs"
-# table reads HKLM + every loaded user hive), so a lingering per-user copy keeps
-# the policy red and leaves two copies on disk.
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
#
-# Pattern A (remove-and-replace): before installing the machine copy, remove any
-# per-user copy so the device converges on a single canonical copy. The machine
-# installer upgrades an existing machine copy in place, so same-scope data is
-# preserved; only the cross-scope (per-user) copy is removed.
-# See https://github.com/fleetdm/fleet/issues/48248.
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
#
-# NOTE: Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own
-# hive — NOT the logged-on user's. Per-user copies must be found under
-# HKEY_USERS\<user SID>, which is what Remove-OtherScopeCopies does below.
-# Removal is best-effort: it never aborts the machine install, and a copy that
-# survives keeps the (truthful) scope-blind policy red rather than false-green.
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "PowerToys*"
+$fmaPublisherLike = "Microsoft Corporation*"
+$fmaFallbackUninstallArgs = "/uninstall /quiet /norestart"
-$ExpectedExitCodes = @(0, 1641, 3010, 1223)
-
-function Get-UninstallExeAndArgs {
+function Get-FmaUninstallExeAndArgs {
param([string]$Command)
# Registry uninstall strings come in three shapes; parse defensively.
- if ($Command -match '^\s*"([^"]+)"\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- } elseif ($Command -match '^\s*(\S+)\s*(.*)$') {
- return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() }
- }
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
return $null
}
-function Remove-OtherScopeCopies {
+function Remove-FmaOtherScopeCopies {
param(
- [Parameter(Mandatory = $true)][string]$DisplayNameLike,
- [string]$PublisherLike = ''
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
)
- # Per-user uninstall registrations live in the logged-on users' hives.
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
$roots = @()
- foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
- if ($hive.Name -match '_Classes$') { continue }
- # Real interactive users only (skip .DEFAULT and service SIDs).
- if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
- $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
- $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
}
foreach ($root in $roots) {
@@ -54,63 +59,57 @@
if ($key.DisplayName -notlike $DisplayNameLike) { continue }
if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
- $command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
- if (-not $command) {
- Write-Host "Per-user copy '$($key.DisplayName)' has no uninstall string; skipping."
- continue
- }
-
- $parsed = Get-UninstallExeAndArgs $command
- if (-not $parsed) {
- Write-Host "Could not parse uninstall string for '$($key.DisplayName)': $command"
- continue
- }
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
$exe = $parsed.Exe
- $uninstallArgs = $parsed.Args
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
- # PowerToys installers are WiX Burn bundles. Ensure a quiet uninstall.
- if ($exe -match '(?i)msiexec') {
- $uninstallArgs = $uninstallArgs -replace '(?i)/i(\{)', '/x$1'
- if ($uninstallArgs -notmatch '(?i)/x') { $uninstallArgs = ("/x $uninstallArgs").Trim() }
- if ($uninstallArgs -notmatch '(?i)/qn') { $uninstallArgs = ("$uninstallArgs /qn").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
- } else {
- if ($uninstallArgs -notmatch '(?i)/uninstall') { $uninstallArgs = ("$uninstallArgs /uninstall").Trim() }
- if ($uninstallArgs -notmatch '(?i)/quiet') { $uninstallArgs = ("$uninstallArgs /quiet").Trim() }
- if ($uninstallArgs -notmatch '(?i)/norestart') { $uninstallArgs = ("$uninstallArgs /norestart").Trim() }
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
}
- if (-not ($exe -match '(?i)msiexec') -and -not (Test-Path -LiteralPath $exe)) {
- Write-Host "Per-user uninstaller missing on disk for '$($key.DisplayName)': $exe"
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
continue
}
- Write-Host "Removing per-user copy: '$($key.DisplayName)'"
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
Write-Host " Command: $exe"
- Write-Host " Args: $uninstallArgs"
+ Write-Host " Args: $uargs"
try {
$opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
- if ($uninstallArgs -ne '') { $opts.ArgumentList = $uninstallArgs }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
$p = Start-Process @opts
- Write-Host " Per-user uninstall exit code: $($p.ExitCode)"
+ Write-Host " Exit code: $($p.ExitCode)"
} catch {
- # Best effort: never fail the machine install because of other-scope cleanup.
- Write-Host " WARNING: failed to remove per-user copy: $_"
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
}
}
}
}
try {
- Stop-Process -Name "PowerToys" -Force -ErrorAction SilentlyContinue
- Remove-OtherScopeCopies -DisplayNameLike "PowerToys*" -PublisherLike "Microsoft Corporation*"
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
} catch {
- # Cleanup is best-effort; proceed to install regardless.
- Write-Host "Warning during per-user cleanup: $_"
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
}
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
+$ExpectedExitCodes = @(0, 1641, 3010, 1223)
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/slack/windows.json=== Install // 8b41f934 -> b6e159c4 ===
--- /tmp/old.Un8u8D 2026-07-02 04:06:43.517756132 +0000
+++ /tmp/new.UUkKh8 2026-07-02 04:06:43.518756143 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Slack'; Publisher = 'Slack Technologies*'; FallbackArgs = '--uninstall -s' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/visual-studio-code/windows.json=== Install // 49122823 -> 2c16ed6d ===
--- /tmp/old.Mww1wu 2026-07-02 04:06:43.569756729 +0000
+++ /tmp/new.CKSPjO 2026-07-02 04:06:43.569756729 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Microsoft Visual Studio Code*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Learn more about .exe install scripts:
# http://fleetdm.com/learn-more-about/exe-install-scripts
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vivaldi/windows.json=== Install // d5d49757 -> d9389dbd ===
--- /tmp/old.3ePG0D 2026-07-02 04:06:43.621757325 +0000
+++ /tmp/new.fta5p5 2026-07-02 04:06:43.621757325 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Vivaldi*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "--uninstall --force-uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
# Install Vivaldi silently, machine-wide (Chromium-based browser).
# Fleet runs installs as SYSTEM, so --system-level is required to install for
# all users under %ProgramFiles% (and register under HKLM). Without it the
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/vscodium/windows.json=== Install // 38852240 -> 808c2033 ===
--- /tmp/old.Q7UWqW 2026-07-02 04:06:43.688758095 +0000
+++ /tmp/new.tfdxnV 2026-07-02 04:06:43.690758118 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "VSCodium*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$exeFilePath = "${env:INSTALLER_PATH}"
try {
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windows-app/windows.json=== Install // f3fab53b -> 86e2a0dd ===
--- /tmp/old.E8AYOq 2026-07-02 04:06:43.745758749 +0000
+++ /tmp/new.N9Jbcy 2026-07-02 04:06:43.746758760 +0000
@@ -1,3 +1,148 @@
+# Fleet Pattern A (MSIX): converge this app on the MSIX package
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app as an MSIX package, but the app also shipped (or still
+# ships) as a Win32 installer (exe/MSI). A leftover Win32 copy at ANY scope keeps
+# the scope-blind patch policy (osquery's "programs" table) red while the MSIX is
+# current, and leaves a stale, unmanaged copy on disk. Unlike the dual-variant
+# Win32 apps -- where only the opposite scope is swept -- every Win32 copy of an
+# MSIX-managed app is legacy, so BOTH Win32 uninstall hives are swept: HKLM, and
+# HKEY_USERS for per-user installs (Fleet runs as SYSTEM, where HKCU maps to
+# SYSTEM's own hive, so per-user copies are found under HKEY_USERS).
+#
+# Guards:
+# - DisplayName matching is exact, mirrors the detection query's names, and
+# requires a publisher match, so unrelated software can't match.
+# - MSIX registrations are never touched: PackageFullName-style keys and entries
+# under \WindowsApps\ are skipped, so a re-run can't remove the package this
+# script just installed.
+# - Entries with no quiet uninstall path are skipped: a raw UninstallString run
+# as SYSTEM can hang on UI until Fleet's script timeout kills the install.
+# - A per-user uninstaller launched by SYSTEM removes its files but cannot clean
+# the user's HKEY_USERS uninstall key (it writes to HKCU, which is SYSTEM's
+# hive). That phantom entry would keep the policy red forever, so the matched
+# key is deleted -- but ONLY after verifying the uninstaller removed itself
+# from disk. If files remain, the key stays and the policy stays truthfully red.
+# - Best-effort: cleanup never aborts the MSIX install below.
+#
+# Data note: Win32 -> MSIX is a cross-packaging move; local app data does not
+# carry into the MSIX container (account-based/server-synced data survives).
+
+$fmaWin32Matchers = @(
+ @{ Name = 'Windows App'; Publisher = 'Microsoft*'; FallbackArgs = '' }
+)
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaWin32Copies {
+ param([array]$Matchers)
+
+ # Every Win32 scope is legacy for an MSIX-managed app: sweep HKLM and all
+ # real interactive users' hives (skip .DEFAULT, service SIDs, _Classes).
+ $roots = @(
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+ 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ )
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ # MSIX self-guard: a PackageFullName-style key name means an MSIX
+ # registration, never a legacy Win32 copy.
+ if ($sub.PSChildName -match '_[a-z0-9]{13}$') { continue }
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ("$($key.UninstallString)$($key.InstallLocation)" -match '(?i)\\WindowsApps\\') { continue }
+
+ $matcher = $null
+ foreach ($m in $Matchers) {
+ if ($key.DisplayName -like $m.Name -and ($m.Publisher -eq '' -or $key.Publisher -like $m.Publisher)) {
+ $matcher = $m
+ break
+ }
+ }
+ if (-not $matcher) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim) {
+ if ($matcher.FallbackArgs -eq '') {
+ Write-Host "Fleet: no quiet uninstall path for legacy copy '$($key.DisplayName)', leaving it (policy stays red)"
+ continue
+ }
+ $uargs = ("$uargs $($matcher.FallbackArgs)").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: legacy Win32 uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing legacy Win32 copy '$($key.DisplayName)' from $root"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove legacy Win32 copy: $_"
+ continue
+ }
+
+ # Per-user uninstallers can't clean their HKEY_USERS key when run as
+ # SYSTEM. Remove the matched key only after the uninstaller is
+ # verifiably gone from disk (Squirrel-style uninstallers self-delete
+ # with a short delay, hence the settle time).
+ if ($root -like 'Registry::HKEY_USERS*' -and -not $isMsi) {
+ Start-Sleep -Seconds 5
+ if (-not (Test-Path -LiteralPath $exe)) {
+ Remove-Item -Path $sub.PSPath -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Host " Removed leftover per-user uninstall registry entry"
+ } else {
+ Write-Host " Uninstaller still on disk; leaving registry entry (policy stays red)"
+ }
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaWin32Copies -Matchers $fmaWin32Matchers
+} catch {
+ Write-Host "Fleet: warning during legacy Win32 cleanup: $_"
+}
+
+# ---- MSIX install ----
+
# MSIX: provision machine-wide so the app is available to all users at sign-in, then
# opportunistically register for the currently logged-on console user (via a scheduled
# task in their session) so the app is immediately visible without requiring sign-out.
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/windsurf/windows.json=== Install // 232abd06 -> 044c2bcd ===
--- /tmp/old.glBy4S 2026-07-02 04:06:43.815759553 +0000
+++ /tmp/new.C2Evzb 2026-07-02 04:06:43.815759553 +0000
@@ -1,3 +1,112 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Windsurf*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
# install switches:
# /VERYSILENT = no UI at all
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zoom/windows.json=== Install // 8959087b -> a6f3a656 ===
--- /tmp/old.8o9Qmv 2026-07-02 04:06:43.869760173 +0000
+++ /tmp/new.HGKInO 2026-07-02 04:06:43.869760173 +0000
@@ -1,3 +1,113 @@
+# Fleet Pattern A: converge this app on a single install scope
+# (https://github.com/fleetdm/fleet/issues/48248).
+#
+# Fleet manages this app at "machine" scope. Windows also offers the opposite
+# scope, so a host may already have a stale copy there. The patch policy is
+# scope-blind (osquery's "programs" table reads HKLM + every loaded user hive),
+# so a lingering opposite-scope copy keeps the policy red and leaves two copies
+# on disk. Remove the opposite-scope copy before installing the managed copy so
+# the device converges on one canonical copy; a same-scope upgrade is left to the
+# installer below (preserves data).
+#
+# Fleet runs install scripts as SYSTEM, where HKCU maps to SYSTEM's own hive --
+# NOT the logged-on user's -- so per-user copies are found under HKEY_USERS.
+# Removal is best-effort: it never aborts the install, and a copy that survives
+# keeps the (truthful) scope-blind policy red rather than false-green.
+
+$fmaTargetScope = "machine"
+$fmaDisplayNameLike = "Zoom*"
+$fmaPublisherLike = ""
+$fmaFallbackUninstallArgs = "/uninstall"
+
+function Get-FmaUninstallExeAndArgs {
+ param([string]$Command)
+ # Registry uninstall strings come in three shapes; parse defensively.
+ if ($Command -match '^\s*"([^"]+)"\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '(?i)^\s*(.+?\.exe)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ elseif ($Command -match '^\s*(\S+)\s*(.*)$') { return @{ Exe = $Matches[1]; Args = $Matches[2].Trim() } }
+ return $null
+}
+
+function Remove-FmaOtherScopeCopies {
+ param(
+ [string]$TargetScope,
+ [string]$DisplayNameLike,
+ [string]$PublisherLike,
+ [string]$FallbackArgs
+ )
+
+ # Scan ONLY the opposite scope's uninstall hives, so a broad DisplayName match
+ # can't touch the copy Fleet manages.
+ $roots = @()
+ if ($TargetScope -eq 'machine') {
+ foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {
+ if ($hive.Name -match '_Classes$') { continue }
+ # Real interactive users only (skip .DEFAULT and service SIDs).
+ if ($hive.PSChildName -notmatch '^S-1-5-21-') { continue }
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ $roots += "Registry::$($hive.Name)\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ }
+ } else {
+ $roots += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+ $roots += 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
+ }
+
+ foreach ($root in $roots) {
+ foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {
+ $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue
+ if (-not $key.DisplayName) { continue }
+ if ($key.DisplayName -notlike $DisplayNameLike) { continue }
+ if ($PublisherLike -ne '' -and $key.Publisher -notlike $PublisherLike) { continue }
+
+ # Prefer the vendor's QuietUninstallString verbatim; it already carries
+ # the correct silent switches for that installer technology.
+ $useVerbatim = [bool]$key.QuietUninstallString
+ $command = if ($useVerbatim) { $key.QuietUninstallString } else { $key.UninstallString }
+ if (-not $command) { continue }
+
+ $parsed = Get-FmaUninstallExeAndArgs $command
+ if (-not $parsed) { Write-Host "Fleet: could not parse uninstall string: $command"; continue }
+ $exe = $parsed.Exe
+ $uargs = $parsed.Args
+ $isMsi = $exe -match '(?i)msiexec'
+
+ if ($isMsi) {
+ $uargs = $uargs -replace '(?i)/i(\{)', '/x$1'
+ if ($uargs -notmatch '(?i)/x') { $uargs = ("/x $uargs").Trim() }
+ if ($uargs -notmatch '(?i)/qn') { $uargs = ("$uargs /qn").Trim() }
+ if ($uargs -notmatch '(?i)/norestart') { $uargs = ("$uargs /norestart").Trim() }
+ } elseif (-not $useVerbatim -and $FallbackArgs -ne '') {
+ $uargs = ("$uargs $FallbackArgs").Trim()
+ }
+
+ if (-not $isMsi -and -not (Test-Path -LiteralPath $exe)) {
+ Write-Host "Fleet: opposite-scope uninstaller missing on disk: $exe"
+ continue
+ }
+
+ Write-Host "Fleet: removing opposite-scope copy '$($key.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: $uargs"
+ try {
+ $opts = @{ FilePath = $exe; PassThru = $true; Wait = $true; NoNewWindow = $true }
+ if ($uargs -ne '') { $opts.ArgumentList = $uargs }
+ $p = Start-Process @opts
+ Write-Host " Exit code: $($p.ExitCode)"
+ } catch {
+ Write-Host " WARNING: failed to remove opposite-scope copy: $_"
+ }
+ }
+ }
+}
+
+try {
+ Remove-FmaOtherScopeCopies -TargetScope $fmaTargetScope -DisplayNameLike $fmaDisplayNameLike -PublisherLike $fmaPublisherLike -FallbackArgs $fmaFallbackUninstallArgs
+} catch {
+ Write-Host "Fleet: warning during opposite-scope cleanup: $_"
+}
+
+# ---- App install ----
+
$logFile = "${env:TEMP}/fleet-install-software.log"
try {
=== Uninstall Script (no changes) === |
Related issue: Resolves #48248
Fixes the Windows FMA duplicate/stale-copy problem where a patch policy could not distinguish per-user vs per-machine install scope. Covers the foundation work and Pattern A remediation for all dual-variant Windows FMAs.
Root cause
Windows apps can install per-user (
HKCU,%LOCALAPPDATA%) or per-machine (HKLM,Program Files). The FMA patch policy is theexistsdetection query with a version compare appended, andprogramsreads both scopes — so when a host had the app at one scope and the FMA installed at the other, a second copy was created, the stale copy lingered unmanaged, and the scope-blind policy kept reporting the host not patched.What changed
Foundation
ingester.go): trust the input'sinstaller_scopewhen the winget manifest is silent on scope (scope == input.InstallerScope || scope == ""). Previously, setting a scope on an NSIS/Inno/burn app with no declared wingetScopepanicked. Exact matching still disambiguates dual-variant apps.installer_scopeon the 9 previously-unset inputs, each verified from an authoritative source (installer PErequestedExecutionLevel, MSIALLUSERS, or wingetScope) — not guessed.TestInputInstallerScopeIsSetfails CI if any winget input lacks a validmachine/userscope; plus ingester tests for the scope-fallback and scope-mismatch paths.new-fmaskill.Pattern A remediation — all dual-variant apps
Every dual-variant install script now runs a single canonical, best-effort
Remove-FmaOtherScopeCopiesblock before installing the managed copy. It:HKEY_USERS\<SID>for machine-target apps (Fleet runs as SYSTEM, whereHKCUis SYSTEM's own hive),HKLMfor user-target apps — so a broad DisplayName match can't touch the copy Fleet manages;QuietUninstallStringverbatim (correct silent switches per installer tech: Chromium--force-uninstall, Inno/VERYSILENT, MSI/x /qn, …), with a per-app fallback derived from that app's own uninstall script;* MSI apps get a custom install script (cleanup +
msiexec); their uninstall stays upgrade-code based.Pattern A remediation — MSIX apps (legacy Win32 cleanup)
For an MSIX-managed app there is no "same-scope" Win32 copy to leave for the installer — every leftover exe/MSI copy (e.g. old Squirrel per-user Slack, classic Teams + its Machine-Wide Installer MSI) keeps the scope-blind policy red while the MSIX is current. Each MSIX install script now runs a
Remove-FmaWin32Copiesblock beforeAdd-AppxProvisionedPackagethat sweeps both Win32 uninstall hives (HKLM +HKEY_USERS\S-1-5-21-*), with guards the dual-variant block doesn't need:*_<13-char publisher hash>) and entries under\WindowsApps\are never touched, so a re-run can't remove the package the script just installed;Arccan't match ArcGIS and the Teams Meeting Add-in isn't removed;QuietUninstallStringand no known silent fallback are left alone (a rawUninstallStringrun as SYSTEM can hang on UI until the script times out);HKEY_USERSuninstall key (it writes to HKCU = SYSTEM's hive), which would keep the policy red forever — so the matched key is deleted only after verifying the uninstaller removed itself from disk; if files remain, the key stays (truthful red, never false-green).The same-packaging upgrade path (pre-existing older MSIX) needs no script change: provisioning stages the newer version machine-wide and each user's registration upgrades at next sign-in (data preserved), with the console user upgraded immediately via the existing scheduled-task registration.
Detection queries remain scope-blind by design.
programsafter user sign-in with the name/publisher the detection query expects (the validator's premise,cmd/maintained-apps/validate/windows.go); (2) confirm provisioning a newer version upgrades an existing older per-user registration at next sign-in; (3) confirm SquirrelUpdate.exe --uninstall -slaunched as SYSTEM self-deletes within the 5s settle window so the leftoverHKEY_USERSkey cleanup fires; (4) confirm a cleanly-failing (not hanging) provision when a host already has a newer MSIX registered.cmd/maintained-apps/validate/windows.gonow ignores "Microsoft Edge WebView2 Runtime"/"Microsoft Edge Update" when checking Microsoft Edge — the broadLIKE '%Microsoft Edge%'match falsely reported Edge as still installed after uninstall whenever the runner image's WebView2 version coincided with the manifest version (Microsoft version-locks WebView2 to Edge releases; hit on run 28561163533).firefox.exeappeared on disk, but the NSIS installer writes the registry uninstall entry (what detection reads) last — detection could race the still-running installer (hit on run 28562265782). The poll now waits for both the exe and theMozilla Firefox*ESR*uninstall entry.Checklist for submitter
changes/.Test-Path, best-effort, scope-restricted registry scan).-Wait; no unbounded loops.Testing
TestInputInstallerScopeIsSet, ingester scope-fallback/mismatch cases); all 22 apps re-ingest cleanly (outputs differ only byinstall_script_ref).fleetd/orbit/Fleet Desktop