Update Fleet-maintained apps - #46389
Conversation
Generated automatically with cmd/maintained-apps.
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
Script Diff Resultsee/maintained-apps/outputs/arc/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/brave-browser/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/camtasia/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/canva/windows.json=== Install Script (no changes) ===
=== Uninstall // 58022dd8 -> f683286d ===
--- /tmp/old.Y0NIsC 2026-05-28 21:03:38.789449830 +0000
+++ /tmp/new.j867gX 2026-05-28 21:03:38.790449827 +0000
@@ -32,7 +32,7 @@
# Parse the uninstall string. Handle both quoted and unquoted exe paths.
if ($uninstallString -match '^"([^"]+)"(.*)') {
$exePath = $matches[1]
-} elseif ($uninstallString -match '^([^\s]+)(.*)') {
+} elseif ($uninstallString -match '^(.+?\.exe)(.*)$') {
$exePath = $matches[1]
} else {
Write-Host "Error: Could not parse uninstall string: $uninstallString"ee/maintained-apps/outputs/claude/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dialpad/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/grammarly-desktop/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/granola/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/granola/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/notepad++/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/parallels/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/power-bi/windows.json=== Install // ee068356 -> 4be62464 ===
--- /tmp/old.zqfkjX 2026-05-28 21:03:39.059448957 +0000
+++ /tmp/new.aDPG4i 2026-05-28 21:03:39.059448957 +0000
@@ -1,12 +1,8 @@
-# Learn more about .exe install scripts:
-# http://fleetdm.com/learn-more-about/exe-install-scripts
-
$exeFilePath = "${env:INSTALLER_PATH}"
try {
# Add argument to install silently
-# Asana uses --silent for silent installation
$processOptions = @{
FilePath = "$exeFilePath"
ArgumentList = "/silent", "/norestart", "ACCEPT_EULA=1"
=== Uninstall // fa3b8ea6 -> 201475bb ===
--- /tmp/old.ZQWU8D 2026-05-28 21:03:39.079448893 +0000
+++ /tmp/new.moFoxI 2026-05-28 21:03:39.080448889 +0000
@@ -8,13 +8,16 @@
# Removing the MSI directly orphans the bundle: its uninstall then no-ops
# (returns 0) and leaves the "Microsoft PowerBI Desktop (x64)" registration
# behind, which is what Fleet's osquery-based validator keeps detecting.
-# Correct approach: uninstall via the BUNDLE first; it removes the MSI and its
-# own registration. Burn relaunches a cached copy of itself + spawns msiexec
-# asynchronously, so wait for those to finish.
+#
+# Correct approach: uninstall via the BUNDLE first. It removes both the MSI and
+# its own registration. Burn relaunches a cached copy of itself and spawns
+# msiexec asynchronously, so wait for those to finish.
$ExpectedExitCodes = @(0, 1605, 1641, 3010)
$exitCode = 0
+# Power BI's default install folder; used as a safety check before deleting any
+# stale registration that survives a successful uninstall.
$installDirs = @(
(Join-Path $env:ProgramFiles 'Microsoft Power BI Desktop'),
(Join-Path ${env:ProgramFiles(x86)} 'Microsoft Power BI Desktop')
@@ -58,6 +61,8 @@
}
try {
+
+ # Uninstall roots across all hives (matches osquery's "programs" table).
$roots = [System.Collections.Generic.List[string]]::new()
$roots.Add('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
$roots.Add('HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall')
@@ -77,20 +82,23 @@
Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue
}
- # Phase 1: uninstall via the Burn bundle bootstrapper(s) FIRST.
+ # --- Phase 1: uninstall via the Burn bundle bootstrapper(s) FIRST. ---
foreach ($e in ($entries | Where-Object { $_.Command -match "(?i)PBIDesktopSetup.*\.exe" })) {
$exe = Get-ExePath $e.Command
if (-not $exe) { Write-Host "Could not parse bundle exe from: $($e.Command)"; continue }
if (-not (Test-Path -LiteralPath $exe)) { Write-Host "Bundle exe missing: $exe"; continue }
Write-Host "Uninstalling bundle: '$($e.DisplayName)'"
+ Write-Host " Command: $exe"
+ Write-Host " Args: /uninstall /quiet /norestart"
$p = Start-Process -FilePath $exe -ArgumentList "/uninstall /quiet /norestart" -PassThru -Wait
Write-Host " Exit code: $($p.ExitCode)"
if (($ExpectedExitCodes -notcontains $p.ExitCode) -and ($exitCode -eq 0)) { $exitCode = $p.ExitCode }
+
Wait-ForProcessExit -Names @("PBIDesktopSetup_x64", "PBIDesktopSetup", "msiexec") -TimeoutSeconds 240
}
- # Phase 2: remove any MSI entries the bundle didn't clean up.
+ # --- Phase 2: remove any MSI entries the bundle didn't clean up. ---
foreach ($e in (Get-PowerBIEntries -Roots $roots)) {
$msiCode = $null
if ($e.Command -match "(?i)MsiExec\.exe\s+/[IX]\s*(\{[A-F0-9-]+\})") { $msiCode = $Matches[1] }
@@ -104,16 +112,22 @@
if (($ExpectedExitCodes -notcontains $p.ExitCode) -and ($exitCode -eq 0)) { $exitCode = $p.ExitCode }
}
- # Phase 3: safety net for stale registration when product files are gone.
+ # --- Phase 3: safety net. If the product files are gone but a stale ARP
+ # registration lingers, remove the orphaned key so detection clears. Gated
+ # on the install folder being absent so we never hide a real install. ---
$productGone = -not ($installDirs | Where-Object { $_ -and (Test-Path -LiteralPath $_) })
foreach ($e in (Get-PowerBIEntries -Roots $roots)) {
- if ($productGone) {
+ $isMachineKey = $e.KeyPath -like 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\*'
+ if ($productGone -and $isMachineKey) {
Write-Host "Removing orphaned registration: '$($e.DisplayName)' ($($e.KeyPath))"
Remove-Item -Path $e.KeyPath -Recurse -Force -ErrorAction SilentlyContinue
} else {
Write-Host "WARNING: entry still present and product files remain: '$($e.DisplayName)'"
if ($exitCode -eq 0) { $exitCode = 1 }
}
+ Write-Host "WARNING: entry still present and product files remain: '$($e.DisplayName)'"
+ if ($exitCode -eq 0) { $exitCode = 1 }
+ }
}
} catch { |
WalkthroughThis PR updates 10 maintained application definitions across macOS and Windows platforms within the Fleet ecosystem. Most updates follow a consistent pattern: version number bumps synchronized with SQL patch-detection thresholds, new installer URLs, and updated SHA256 checksums. Arc, Brave Browser, Camtasia, Claude, Dialpad, Grammarly Desktop, Parallels, and Granola all receive macOS version increments, while Granola and Notepad++ are updated on Windows. Two applications receive more extensive changes: Canva's Windows uninstall logic is refined to improve registry path parsing, and Power BI's Windows release is bumped alongside updated PowerShell install and uninstall scripts that enhance logging and maintain the existing multi-phase uninstall orchestration. Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ee/maintained-apps/outputs/power-bi/windows.json`:
- Line 19: Phase 3 of the uninstall (the block using $productGone and iterating
Get-PowerBIEntries) contains a duplicated WARNING output and an extra closing
brace that breaks the try/catch structure; edit the block in the
Get-PowerBIEntries -> Phase 3 section to remove the duplicated lines "Write-Host
\"WARNING: entry still present and product files remain: '$($e.DisplayName)'\""
and the redundant "if ($exitCode -eq 0) { $exitCode = 1 }" duplicate, then
remove the surplus "}" so the foreach and try/catch close correctly (ensure the
foreach that tests $productGone, the Remove-Item call, and the corresponding
else branch are balanced and the catch block remains intact).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e93862a5-6200-4d47-a595-ca69ec4d62a3
📒 Files selected for processing (12)
ee/maintained-apps/outputs/arc/darwin.jsonee/maintained-apps/outputs/brave-browser/darwin.jsonee/maintained-apps/outputs/camtasia/darwin.jsonee/maintained-apps/outputs/canva/windows.jsonee/maintained-apps/outputs/claude/darwin.jsonee/maintained-apps/outputs/dialpad/darwin.jsonee/maintained-apps/outputs/grammarly-desktop/darwin.jsonee/maintained-apps/outputs/granola/darwin.jsonee/maintained-apps/outputs/granola/windows.jsonee/maintained-apps/outputs/notepad++/windows.jsonee/maintained-apps/outputs/parallels/darwin.jsonee/maintained-apps/outputs/power-bi/windows.json
| "refs": { | ||
| "ee068356": "# Learn more about .exe install scripts:\n# http://fleetdm.com/learn-more-about/exe-install-scripts\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\ntry {\n\n# Add argument to install silently\n# Asana uses --silent for silent installation\n$processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"/silent\", \"/norestart\", \"ACCEPT_EULA=1\"\n PassThru = $true\n Wait = $true\n}\n\n# Start process and track exit code\n$process = Start-Process @processOptions\n$exitCode = $process.ExitCode\n\n# Prints the exit code\nWrite-Host \"Install exit code: $exitCode\"\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n", | ||
| "fa3b8ea6": "# Power BI Desktop's EXE installer is a WiX \"Burn\" bundle. It registers TWO\n# uninstall entries:\n# * \"Microsoft PowerBI Desktop (x64)\" -> the bundle bootstrapper\n# (...\\Package Cache\\{afa18d15-...}\\PBIDesktopSetup_x64.exe)\n# * \"Microsoft Power BI Desktop (x64)\" -> the MSI the bundle installed\n# (MsiExec.exe /X{c7d2053f-...})\n#\n# Removing the MSI directly orphans the bundle: its uninstall then no-ops\n# (returns 0) and leaves the \"Microsoft PowerBI Desktop (x64)\" registration\n# behind, which is what Fleet's osquery-based validator keeps detecting.\n# Correct approach: uninstall via the BUNDLE first; it removes the MSI and its\n# own registration. Burn relaunches a cached copy of itself + spawns msiexec\n# asynchronously, so wait for those to finish.\n\n$ExpectedExitCodes = @(0, 1605, 1641, 3010)\n$exitCode = 0\n\n$installDirs = @(\n (Join-Path $env:ProgramFiles 'Microsoft Power BI Desktop'),\n (Join-Path ${env:ProgramFiles(x86)} 'Microsoft Power BI Desktop')\n)\n\nfunction Wait-ForProcessExit {\n param([string[]]$Names, [int]$TimeoutSeconds = 240)\n $elapsed = 0\n while ($elapsed -lt $TimeoutSeconds) {\n $running = $Names | Where-Object { Get-Process -Name $_ -ErrorAction SilentlyContinue }\n if (-not $running) { break }\n Start-Sleep -Seconds 3\n $elapsed += 3\n }\n}\n\nfunction Get-PowerBIEntries {\n param([string[]]$Roots)\n $list = @()\n foreach ($root in $Roots) {\n foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {\n $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue\n if (-not $key.DisplayName) { continue }\n if (-not (($key.DisplayName -replace '\\s', '').ToLower().Contains(\"powerbidesktop\"))) { continue }\n $list += [PSCustomObject]@{\n DisplayName = $key.DisplayName\n KeyPath = $sub.PSPath\n KeyName = $sub.PSChildName\n Command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }\n }\n }\n }\n return $list\n}\n\nfunction Get-ExePath {\n param([string]$Command)\n if ($Command -match '\"([^\"]+\\.exe)\"') { return $Matches[1] }\n if ($Command -match '(?i)([A-Z]:\\\\[^\"]+?\\.exe)') { return $Matches[1] }\n return $null\n}\n\ntry {\n $roots = [System.Collections.Generic.List[string]]::new()\n $roots.Add('HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall')\n $roots.Add('HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall')\n foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {\n if ($hive.Name -match '_Classes$') { continue }\n $roots.Add(\"Registry::$($hive.Name)\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\")\n $roots.Add(\"Registry::$($hive.Name)\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\")\n }\n\n $entries = Get-PowerBIEntries -Roots $roots\n if ($entries.Count -eq 0) {\n Write-Host \"No Power BI Desktop entries found (already removed).\"\n Exit 0\n }\n\n foreach ($proc in @(\"PBIDesktop\", \"msmdsrv\", \"Microsoft.Mashup.Container\")) {\n Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue\n }\n\n # Phase 1: uninstall via the Burn bundle bootstrapper(s) FIRST.\n foreach ($e in ($entries | Where-Object { $_.Command -match \"(?i)PBIDesktopSetup.*\\.exe\" })) {\n $exe = Get-ExePath $e.Command\n if (-not $exe) { Write-Host \"Could not parse bundle exe from: $($e.Command)\"; continue }\n if (-not (Test-Path -LiteralPath $exe)) { Write-Host \"Bundle exe missing: $exe\"; continue }\n\n Write-Host \"Uninstalling bundle: '$($e.DisplayName)'\"\n $p = Start-Process -FilePath $exe -ArgumentList \"/uninstall /quiet /norestart\" -PassThru -Wait\n Write-Host \" Exit code: $($p.ExitCode)\"\n if (($ExpectedExitCodes -notcontains $p.ExitCode) -and ($exitCode -eq 0)) { $exitCode = $p.ExitCode }\n Wait-ForProcessExit -Names @(\"PBIDesktopSetup_x64\", \"PBIDesktopSetup\", \"msiexec\") -TimeoutSeconds 240\n }\n\n # Phase 2: remove any MSI entries the bundle didn't clean up.\n foreach ($e in (Get-PowerBIEntries -Roots $roots)) {\n $msiCode = $null\n if ($e.Command -match \"(?i)MsiExec\\.exe\\s+/[IX]\\s*(\\{[A-F0-9-]+\\})\") { $msiCode = $Matches[1] }\n elseif ($e.KeyName -match \"(?i)^\\{[A-F0-9-]+\\}$\") { $msiCode = $e.KeyName }\n if (-not $msiCode) { continue }\n\n Write-Host \"Removing leftover MSI: '$($e.DisplayName)' ($msiCode)\"\n $p = Start-Process -FilePath \"MsiExec.exe\" -ArgumentList \"/X $msiCode /qn /norestart\" -PassThru -Wait\n Write-Host \" Exit code: $($p.ExitCode)\"\n Wait-ForProcessExit -Names @(\"msiexec\") -TimeoutSeconds 180\n if (($ExpectedExitCodes -notcontains $p.ExitCode) -and ($exitCode -eq 0)) { $exitCode = $p.ExitCode }\n }\n\n # Phase 3: safety net for stale registration when product files are gone.\n $productGone = -not ($installDirs | Where-Object { $_ -and (Test-Path -LiteralPath $_) })\n foreach ($e in (Get-PowerBIEntries -Roots $roots)) {\n if ($productGone) {\n Write-Host \"Removing orphaned registration: '$($e.DisplayName)' ($($e.KeyPath))\"\n Remove-Item -Path $e.KeyPath -Recurse -Force -ErrorAction SilentlyContinue\n } else {\n Write-Host \"WARNING: entry still present and product files remain: '$($e.DisplayName)'\"\n if ($exitCode -eq 0) { $exitCode = 1 }\n }\n }\n\n} catch {\n Write-Host \"Error: $_\"\n $exitCode = 1\n}\n\nif ($ExpectedExitCodes -contains $exitCode) { Exit 0 } else { Exit $exitCode }\n" | ||
| "201475bb": "# Power BI Desktop's EXE installer is a WiX \"Burn\" bundle. It registers TWO\n# uninstall entries:\n# * \"Microsoft PowerBI Desktop (x64)\" -> the bundle bootstrapper\n# (...\\Package Cache\\{afa18d15-...}\\PBIDesktopSetup_x64.exe)\n# * \"Microsoft Power BI Desktop (x64)\" -> the MSI the bundle installed\n# (MsiExec.exe /X{c7d2053f-...})\n#\n# Removing the MSI directly orphans the bundle: its uninstall then no-ops\n# (returns 0) and leaves the \"Microsoft PowerBI Desktop (x64)\" registration\n# behind, which is what Fleet's osquery-based validator keeps detecting.\n#\n# Correct approach: uninstall via the BUNDLE first. It removes both the MSI and\n# its own registration. Burn relaunches a cached copy of itself and spawns\n# msiexec asynchronously, so wait for those to finish.\n\n$ExpectedExitCodes = @(0, 1605, 1641, 3010)\n$exitCode = 0\n\n# Power BI's default install folder; used as a safety check before deleting any\n# stale registration that survives a successful uninstall.\n$installDirs = @(\n (Join-Path $env:ProgramFiles 'Microsoft Power BI Desktop'),\n (Join-Path ${env:ProgramFiles(x86)} 'Microsoft Power BI Desktop')\n)\n\nfunction Wait-ForProcessExit {\n param([string[]]$Names, [int]$TimeoutSeconds = 240)\n $elapsed = 0\n while ($elapsed -lt $TimeoutSeconds) {\n $running = $Names | Where-Object { Get-Process -Name $_ -ErrorAction SilentlyContinue }\n if (-not $running) { break }\n Start-Sleep -Seconds 3\n $elapsed += 3\n }\n}\n\nfunction Get-PowerBIEntries {\n param([string[]]$Roots)\n $list = @()\n foreach ($root in $Roots) {\n foreach ($sub in (Get-ChildItem -Path $root -ErrorAction SilentlyContinue)) {\n $key = Get-ItemProperty $sub.PSPath -ErrorAction SilentlyContinue\n if (-not $key.DisplayName) { continue }\n if (-not (($key.DisplayName -replace '\\s', '').ToLower().Contains(\"powerbidesktop\"))) { continue }\n $list += [PSCustomObject]@{\n DisplayName = $key.DisplayName\n KeyPath = $sub.PSPath\n KeyName = $sub.PSChildName\n Command = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }\n }\n }\n }\n return $list\n}\n\nfunction Get-ExePath {\n param([string]$Command)\n if ($Command -match '\"([^\"]+\\.exe)\"') { return $Matches[1] }\n if ($Command -match '(?i)([A-Z]:\\\\[^\"]+?\\.exe)') { return $Matches[1] }\n return $null\n}\n\ntry {\n\n # Uninstall roots across all hives (matches osquery's \"programs\" table).\n $roots = [System.Collections.Generic.List[string]]::new()\n $roots.Add('HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall')\n $roots.Add('HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall')\n foreach ($hive in (Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction SilentlyContinue)) {\n if ($hive.Name -match '_Classes$') { continue }\n $roots.Add(\"Registry::$($hive.Name)\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\")\n $roots.Add(\"Registry::$($hive.Name)\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\")\n }\n\n $entries = Get-PowerBIEntries -Roots $roots\n if ($entries.Count -eq 0) {\n Write-Host \"No Power BI Desktop entries found (already removed).\"\n Exit 0\n }\n\n foreach ($proc in @(\"PBIDesktop\", \"msmdsrv\", \"Microsoft.Mashup.Container\")) {\n Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue\n }\n\n # --- Phase 1: uninstall via the Burn bundle bootstrapper(s) FIRST. ---\n foreach ($e in ($entries | Where-Object { $_.Command -match \"(?i)PBIDesktopSetup.*\\.exe\" })) {\n $exe = Get-ExePath $e.Command\n if (-not $exe) { Write-Host \"Could not parse bundle exe from: $($e.Command)\"; continue }\n if (-not (Test-Path -LiteralPath $exe)) { Write-Host \"Bundle exe missing: $exe\"; continue }\n\n Write-Host \"Uninstalling bundle: '$($e.DisplayName)'\"\n Write-Host \" Command: $exe\"\n Write-Host \" Args: /uninstall /quiet /norestart\"\n $p = Start-Process -FilePath $exe -ArgumentList \"/uninstall /quiet /norestart\" -PassThru -Wait\n Write-Host \" Exit code: $($p.ExitCode)\"\n if (($ExpectedExitCodes -notcontains $p.ExitCode) -and ($exitCode -eq 0)) { $exitCode = $p.ExitCode }\n\n Wait-ForProcessExit -Names @(\"PBIDesktopSetup_x64\", \"PBIDesktopSetup\", \"msiexec\") -TimeoutSeconds 240\n }\n\n # --- Phase 2: remove any MSI entries the bundle didn't clean up. ---\n foreach ($e in (Get-PowerBIEntries -Roots $roots)) {\n $msiCode = $null\n if ($e.Command -match \"(?i)MsiExec\\.exe\\s+/[IX]\\s*(\\{[A-F0-9-]+\\})\") { $msiCode = $Matches[1] }\n elseif ($e.KeyName -match \"(?i)^\\{[A-F0-9-]+\\}$\") { $msiCode = $e.KeyName }\n if (-not $msiCode) { continue }\n\n Write-Host \"Removing leftover MSI: '$($e.DisplayName)' ($msiCode)\"\n $p = Start-Process -FilePath \"MsiExec.exe\" -ArgumentList \"/X $msiCode /qn /norestart\" -PassThru -Wait\n Write-Host \" Exit code: $($p.ExitCode)\"\n Wait-ForProcessExit -Names @(\"msiexec\") -TimeoutSeconds 180\n if (($ExpectedExitCodes -notcontains $p.ExitCode) -and ($exitCode -eq 0)) { $exitCode = $p.ExitCode }\n }\n\n # --- Phase 3: safety net. If the product files are gone but a stale ARP\n # registration lingers, remove the orphaned key so detection clears. Gated\n # on the install folder being absent so we never hide a real install. ---\n $productGone = -not ($installDirs | Where-Object { $_ -and (Test-Path -LiteralPath $_) })\n foreach ($e in (Get-PowerBIEntries -Roots $roots)) {\n $isMachineKey = $e.KeyPath -like 'Microsoft.PowerShell.Core\\Registry::HKEY_LOCAL_MACHINE\\*'\n if ($productGone -and $isMachineKey) {\n Write-Host \"Removing orphaned registration: '$($e.DisplayName)' ($($e.KeyPath))\"\n Remove-Item -Path $e.KeyPath -Recurse -Force -ErrorAction SilentlyContinue\n } else {\n Write-Host \"WARNING: entry still present and product files remain: '$($e.DisplayName)'\"\n if ($exitCode -eq 0) { $exitCode = 1 }\n }\n Write-Host \"WARNING: entry still present and product files remain: '$($e.DisplayName)'\"\n if ($exitCode -eq 0) { $exitCode = 1 }\n }\n }\n\n} catch {\n Write-Host \"Error: $_\"\n $exitCode = 1\n}\n\nif ($ExpectedExitCodes -contains $exitCode) { Exit 0 } else { Exit $exitCode }\n", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python3 - <<'EOF'
import json
with open('ee/maintained-apps/outputs/power-bi/windows.json') as f:
data = json.load(f)
s = data['refs']['201475bb']
print("open { :", s.count('{'))
print("close } :", s.count('}'))
print("balanced :", s.count('{') == s.count('}'))
EOFRepository: fleetdm/fleet
Length of output: 100
🏁 Script executed:
#!/bin/bash
python3 - <<'PY'
import json, re
path='ee/maintained-apps/outputs/power-bi/windows.json'
with open(path) as f:
data=json.load(f)
s=data['refs']['201475bb']
# Get line-numbered excerpt around Phase 3
lines=s.splitlines()
# Find "Phase 3" line index
i=None
for idx,l in enumerate(lines):
if 'Phase 3' in l:
i=idx
break
if i is None:
raise SystemExit('Phase 3 marker not found')
start=max(0,i-10)
end=min(len(lines), i+80)
for ln in range(start,end):
print(f"{ln+1:04d}: {lines[ln]}")
print("\nWARNING count:", sum(1 for l in lines if 'WARNING: entry still present' in l))
PYRepository: fleetdm/fleet
Length of output: 2018
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "ValidateSoftwareInstallerScript|fleet\.ValidateSoftwareInstallerScript|maintained_apps\.go" -S .Repository: fleetdm/fleet
Length of output: 1514
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import itertools,sys,os
def show(path, start, end):
print(f"\n--- {path}:{start}-{end} ---")
with open(path,'r',encoding='utf-8') as f:
for i,line in enumerate(f, start=1):
if i<start: continue
if i>end: break
sys.stdout.write(f"{i:5d}: {line}")
show('server/fleet/scripts.go', 520, 700)
show('server/fleet/scripts_test.go', 180, 450)
show('ee/server/service/maintained_apps.go', 80, 160)
PYRepository: fleetdm/fleet
Length of output: 20280
🏁 Script executed:
#!/bin/bash
set -euo pipefail
command -v pwsh >/dev/null 2>&1 && echo "pwsh: $(pwsh -NoProfile -Command '$PSVersionTable.PSVersion.ToString()')" || echo "pwsh not found"
command -v powershell >/dev/null 2>&1 && echo "powershell: $(powershell -NoProfile -Command '$PSVersionTable.PSVersion.ToString()')" || echo "powershell not found"Repository: fleetdm/fleet
Length of output: 90
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
path='ee/maintained-apps/outputs/power-bi/windows.json'
with open(path) as f:
data=json.load(f)
s=data['refs']['201475bb']
# Print to a temp file for pwsh parsing
open('/tmp/powerbi_uninstall.ps1','w',encoding='utf-8').write(s)
PY
pwsh -NoProfile -Command '
$script = Get-Content /tmp/powerbi_uninstall.ps1 -Raw
$errors = $null
$tokens = $null
$null = [System.Management.Automation.Language.Parser]::ParseInput($script, [ref]$tokens, [ref]$errors)
"ParseErrorCount=$($errors.Count)"
$errors | ForEach-Object { $_.Message } | Select-Object -First 20
'Repository: fleetdm/fleet
Length of output: 181
Fix Power BI uninstall script Phase 3 syntax (extra } + duplicated WARNING block)
The server-side validator for Windows scripts only checks empty/size/UTF-8 (shebang/interpreter is skipped for windows), so this malformed PowerShell passes validation but fails to parse on hosts. Phase 3 has a duplicated WARNING: ... block and an extra closing brace that breaks the try/catch structure (Try missing Catch/Finally, plus Unexpected token '}').
Proposed fix (remove duplicate WARNING/exit lines and surplus brace in the 201475bb uninstall ref string)
} else {
Write-Host "WARNING: entry still present and product files remain: '$($e.DisplayName)'"
if ($exitCode -eq 0) { $exitCode = 1 }
}
- Write-Host "WARNING: entry still present and product files remain: '$($e.DisplayName)'"
- if ($exitCode -eq 0) { $exitCode = 1 }
- }
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ee/maintained-apps/outputs/power-bi/windows.json` at line 19, Phase 3 of the
uninstall (the block using $productGone and iterating Get-PowerBIEntries)
contains a duplicated WARNING output and an extra closing brace that breaks the
try/catch structure; edit the block in the Get-PowerBIEntries -> Phase 3 section
to remove the duplicated lines "Write-Host \"WARNING: entry still present and
product files remain: '$($e.DisplayName)'\"" and the redundant "if ($exitCode
-eq 0) { $exitCode = 1 }" duplicate, then remove the surplus "}" so the foreach
and try/catch close correctly (ensure the foreach that tests $productGone, the
Remove-Item call, and the corresponding else branch are balanced and the catch
block remains intact).
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
|
Closing in favor of #46394. |
Automated ingestion of latest Fleet-maintained app data.
Summary by CodeRabbit