Add Windows FMAs (digit batch): 3DF Zephyr Free, 4K Video Downloader+#48872
Conversation
… Downloader, 4K Video Downloader+
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #48872 +/- ##
=======================================
Coverage 68.10% 68.10%
=======================================
Files 3694 3697 +3
Lines 234425 234439 +14
Branches 12464 12463 -1
=======================================
+ Hits 159647 159658 +11
- Misses 60450 60453 +3
Partials 14328 14328
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:
|
Validator results: the VD+ ARP entry can be the chained MSI (MsiExec.exe
/X{...}) rather than the burn bundle; prepending /uninstall produced invalid
msiexec args. The uninstall script now prefers the bundle entry and normalizes
msiexec args when only the MSI entry exists. 3DxWare 10's vendor bootstrapper
hangs for 10 minutes in the headless SYSTEM session, so it's removed from this
batch.
Script Diff Resultsee/maintained-apps/outputs/3df-zephyr-free/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/4k-video-downloader-plus/windows.json=== Install Script (no changes) ===
=== Uninstall // 5243d726 -> 9283b0c4 ===
--- /tmp/old.Ujzu2a 2026-07-07 18:23:59.606211784 +0000
+++ /tmp/new.E4m9bV 2026-07-07 18:23:59.606211784 +0000
@@ -1,29 +1,29 @@
-# Uninstalls the 4K Video Downloader+ WiX "burn" bundle.
+# Uninstalls 4K Video Downloader+.
#
-# The app registers a bundle ARP entry (DisplayName "4K Video Downloader+",
-# publisher "InterPromo GMBH") whose UninstallString points at the cached
-# bootstrapper .exe. Burn bundles uninstall by running that .exe with
-# /uninstall /quiet /norestart -- never via msiexec. We look the entry up by
-# its exact DisplayName; the "+" keeps this from matching the separate MSI-based
+# The app is a WiX "burn" bundle that chains an MSI, and the ARP entry with
+# DisplayName "4K Video Downloader+" may be either the bundle (an .exe
+# UninstallString, needs /uninstall /quiet /norestart) or the chained MSI
+# (an MsiExec.exe /X{ProductCode} UninstallString, needs /qn /norestart --
+# NOT /uninstall, which is invalid for msiexec). Handle both shapes. The "+"
+# in the exact-match name keeps this from touching the separate MSI-based
# "4K Video Downloader" product.
$softwareName = "4K Video Downloader+"
-function Invoke-Uninstaller {
- param([string]$exe, [string]$exeArgs)
- if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
- if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
- if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
- $exeArgs = $exeArgs.Trim()
- Write-Host "Uninstall command: $exe"
- Write-Host "Uninstall args: $exeArgs"
- $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
- return $process.ExitCode
-}
-
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
+function Split-UninstallString {
+ param([string]$raw)
+ # Parse into executable + args, handling quoted/unquoted/bare shapes.
+ if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
+ return @($matches[1], $matches[2].Trim())
+ } elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ return @($matches[1], $matches[2].Trim())
+ }
+ return @($raw, "")
+}
+
$exitCode = $null
try {
@@ -33,24 +33,35 @@
-ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath }
-foreach ($key in $uninstallKeys) {
- if ($key.DisplayName -eq $softwareName) {
- $raw = $key.QuietUninstallString
- if (-not $raw) { $raw = $key.UninstallString }
- if (-not $raw) { continue }
-
- # Parse into executable + args, handling quoted/unquoted/bare shapes.
- if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
- $exe = $matches[1]; $exeArgs = $matches[2].Trim()
- } elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- $exe = $matches[1]; $exeArgs = $matches[2].Trim()
- } else {
- $exe = $raw; $exeArgs = ""
- }
+[array]$entries = $uninstallKeys | Where-Object { $_.DisplayName -eq $softwareName }
- $exitCode = Invoke-Uninstaller -exe $exe -exeArgs $exeArgs
- break
+# Prefer the burn bundle entry (non-msiexec .exe uninstaller): it removes the
+# whole chain, including the MSI. Fall back to the chained MSI entry.
+$bundle = $entries | Where-Object {
+ $raw = if ($_.QuietUninstallString) { $_.QuietUninstallString } else { $_.UninstallString }
+ $raw -and $raw -notmatch '(?i)msiexec'
+} | Select-Object -First 1
+$entry = if ($bundle) { $bundle } else { $entries | Select-Object -First 1 }
+
+if ($entry) {
+ $raw = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }
+ $exe, $exeArgs = Split-UninstallString -raw $raw
+
+ if ($exe -match '(?i)msiexec') {
+ if ($exeArgs -notmatch '(?i)/(x|uninstall)') { $exeArgs = "/X $exeArgs" }
+ if ($exeArgs -notmatch '(?i)/(qn|quiet)') { $exeArgs = "$exeArgs /qn" }
+ if ($exeArgs -notmatch '(?i)/norestart') { $exeArgs = "$exeArgs /norestart" }
+ } else {
+ if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
+ if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
+ if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
}
+ $exeArgs = $exeArgs.Trim()
+
+ Write-Host "Uninstall command: $exe"
+ Write-Host "Uninstall args: $exeArgs"
+ $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
+ $exitCode = $process.ExitCode
}
} catch {ee/maintained-apps/outputs/4k-video-downloader/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) === |
The classic 4.x app is in maintenance mode; 4K Video Downloader+ is the actively developed successor, so the catalog offers only the + app.
Script Diff Resultsee/maintained-apps/outputs/3df-zephyr-free/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/4k-video-downloader-plus/windows.json=== Install Script (no changes) ===
=== Uninstall // 5243d726 -> 9283b0c4 ===
--- /tmp/old.HB52CZ 2026-07-07 18:36:33.599429034 +0000
+++ /tmp/new.m8lNlZ 2026-07-07 18:36:33.599429034 +0000
@@ -1,29 +1,29 @@
-# Uninstalls the 4K Video Downloader+ WiX "burn" bundle.
+# Uninstalls 4K Video Downloader+.
#
-# The app registers a bundle ARP entry (DisplayName "4K Video Downloader+",
-# publisher "InterPromo GMBH") whose UninstallString points at the cached
-# bootstrapper .exe. Burn bundles uninstall by running that .exe with
-# /uninstall /quiet /norestart -- never via msiexec. We look the entry up by
-# its exact DisplayName; the "+" keeps this from matching the separate MSI-based
+# The app is a WiX "burn" bundle that chains an MSI, and the ARP entry with
+# DisplayName "4K Video Downloader+" may be either the bundle (an .exe
+# UninstallString, needs /uninstall /quiet /norestart) or the chained MSI
+# (an MsiExec.exe /X{ProductCode} UninstallString, needs /qn /norestart --
+# NOT /uninstall, which is invalid for msiexec). Handle both shapes. The "+"
+# in the exact-match name keeps this from touching the separate MSI-based
# "4K Video Downloader" product.
$softwareName = "4K Video Downloader+"
-function Invoke-Uninstaller {
- param([string]$exe, [string]$exeArgs)
- if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
- if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
- if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
- $exeArgs = $exeArgs.Trim()
- Write-Host "Uninstall command: $exe"
- Write-Host "Uninstall args: $exeArgs"
- $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
- return $process.ExitCode
-}
-
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
+function Split-UninstallString {
+ param([string]$raw)
+ # Parse into executable + args, handling quoted/unquoted/bare shapes.
+ if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
+ return @($matches[1], $matches[2].Trim())
+ } elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ return @($matches[1], $matches[2].Trim())
+ }
+ return @($raw, "")
+}
+
$exitCode = $null
try {
@@ -33,24 +33,35 @@
-ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath }
-foreach ($key in $uninstallKeys) {
- if ($key.DisplayName -eq $softwareName) {
- $raw = $key.QuietUninstallString
- if (-not $raw) { $raw = $key.UninstallString }
- if (-not $raw) { continue }
-
- # Parse into executable + args, handling quoted/unquoted/bare shapes.
- if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
- $exe = $matches[1]; $exeArgs = $matches[2].Trim()
- } elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- $exe = $matches[1]; $exeArgs = $matches[2].Trim()
- } else {
- $exe = $raw; $exeArgs = ""
- }
+[array]$entries = $uninstallKeys | Where-Object { $_.DisplayName -eq $softwareName }
- $exitCode = Invoke-Uninstaller -exe $exe -exeArgs $exeArgs
- break
+# Prefer the burn bundle entry (non-msiexec .exe uninstaller): it removes the
+# whole chain, including the MSI. Fall back to the chained MSI entry.
+$bundle = $entries | Where-Object {
+ $raw = if ($_.QuietUninstallString) { $_.QuietUninstallString } else { $_.UninstallString }
+ $raw -and $raw -notmatch '(?i)msiexec'
+} | Select-Object -First 1
+$entry = if ($bundle) { $bundle } else { $entries | Select-Object -First 1 }
+
+if ($entry) {
+ $raw = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }
+ $exe, $exeArgs = Split-UninstallString -raw $raw
+
+ if ($exe -match '(?i)msiexec') {
+ if ($exeArgs -notmatch '(?i)/(x|uninstall)') { $exeArgs = "/X $exeArgs" }
+ if ($exeArgs -notmatch '(?i)/(qn|quiet)') { $exeArgs = "$exeArgs /qn" }
+ if ($exeArgs -notmatch '(?i)/norestart') { $exeArgs = "$exeArgs /norestart" }
+ } else {
+ if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
+ if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
+ if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
}
+ $exeArgs = $exeArgs.Trim()
+
+ Write-Host "Uninstall command: $exe"
+ Write-Host "Uninstall args: $exeArgs"
+ $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
+ $exitCode = $process.ExitCode
}
} catch { |
Script Diff Resultsee/maintained-apps/outputs/3df-zephyr-free/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/4k-video-downloader-plus/windows.json=== Install Script (no changes) ===
=== Uninstall // 5243d726 -> 9283b0c4 ===
--- /tmp/old.jne7zz 2026-07-07 18:48:34.146434005 +0000
+++ /tmp/new.sra156 2026-07-07 18:48:34.146434005 +0000
@@ -1,29 +1,29 @@
-# Uninstalls the 4K Video Downloader+ WiX "burn" bundle.
+# Uninstalls 4K Video Downloader+.
#
-# The app registers a bundle ARP entry (DisplayName "4K Video Downloader+",
-# publisher "InterPromo GMBH") whose UninstallString points at the cached
-# bootstrapper .exe. Burn bundles uninstall by running that .exe with
-# /uninstall /quiet /norestart -- never via msiexec. We look the entry up by
-# its exact DisplayName; the "+" keeps this from matching the separate MSI-based
+# The app is a WiX "burn" bundle that chains an MSI, and the ARP entry with
+# DisplayName "4K Video Downloader+" may be either the bundle (an .exe
+# UninstallString, needs /uninstall /quiet /norestart) or the chained MSI
+# (an MsiExec.exe /X{ProductCode} UninstallString, needs /qn /norestart --
+# NOT /uninstall, which is invalid for msiexec). Handle both shapes. The "+"
+# in the exact-match name keeps this from touching the separate MSI-based
# "4K Video Downloader" product.
$softwareName = "4K Video Downloader+"
-function Invoke-Uninstaller {
- param([string]$exe, [string]$exeArgs)
- if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
- if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
- if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
- $exeArgs = $exeArgs.Trim()
- Write-Host "Uninstall command: $exe"
- Write-Host "Uninstall args: $exeArgs"
- $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
- return $process.ExitCode
-}
-
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
+function Split-UninstallString {
+ param([string]$raw)
+ # Parse into executable + args, handling quoted/unquoted/bare shapes.
+ if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
+ return @($matches[1], $matches[2].Trim())
+ } elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+ return @($matches[1], $matches[2].Trim())
+ }
+ return @($raw, "")
+}
+
$exitCode = $null
try {
@@ -33,24 +33,35 @@
-ErrorAction SilentlyContinue |
ForEach-Object { Get-ItemProperty $_.PSPath }
-foreach ($key in $uninstallKeys) {
- if ($key.DisplayName -eq $softwareName) {
- $raw = $key.QuietUninstallString
- if (-not $raw) { $raw = $key.UninstallString }
- if (-not $raw) { continue }
-
- # Parse into executable + args, handling quoted/unquoted/bare shapes.
- if ($raw -match '^\s*"([^"]+)"\s*(.*)$') {
- $exe = $matches[1]; $exeArgs = $matches[2].Trim()
- } elseif ($raw -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
- $exe = $matches[1]; $exeArgs = $matches[2].Trim()
- } else {
- $exe = $raw; $exeArgs = ""
- }
+[array]$entries = $uninstallKeys | Where-Object { $_.DisplayName -eq $softwareName }
- $exitCode = Invoke-Uninstaller -exe $exe -exeArgs $exeArgs
- break
+# Prefer the burn bundle entry (non-msiexec .exe uninstaller): it removes the
+# whole chain, including the MSI. Fall back to the chained MSI entry.
+$bundle = $entries | Where-Object {
+ $raw = if ($_.QuietUninstallString) { $_.QuietUninstallString } else { $_.UninstallString }
+ $raw -and $raw -notmatch '(?i)msiexec'
+} | Select-Object -First 1
+$entry = if ($bundle) { $bundle } else { $entries | Select-Object -First 1 }
+
+if ($entry) {
+ $raw = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }
+ $exe, $exeArgs = Split-UninstallString -raw $raw
+
+ if ($exe -match '(?i)msiexec') {
+ if ($exeArgs -notmatch '(?i)/(x|uninstall)') { $exeArgs = "/X $exeArgs" }
+ if ($exeArgs -notmatch '(?i)/(qn|quiet)') { $exeArgs = "$exeArgs /qn" }
+ if ($exeArgs -notmatch '(?i)/norestart') { $exeArgs = "$exeArgs /norestart" }
+ } else {
+ if ($exeArgs -notmatch '/uninstall') { $exeArgs = "/uninstall $exeArgs" }
+ if ($exeArgs -notmatch '/quiet') { $exeArgs = "$exeArgs /quiet" }
+ if ($exeArgs -notmatch '/norestart') { $exeArgs = "$exeArgs /norestart" }
}
+ $exeArgs = $exeArgs.Trim()
+
+ Write-Host "Uninstall command: $exe"
+ Write-Host "Uninstall args: $exeArgs"
+ $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait
+ $exitCode = $process.ExitCode
}
} catch { |
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (12)
WalkthroughThis PR adds two new maintained Windows applications: "3DF Zephyr Free" and "4K Video Downloader+". For each app, it introduces a winget input JSON manifest, install and uninstall PowerShell scripts (Inno Setup silent flags for the former, WiX burn/MSI handling with reboot-code awareness for the latter), a generated output windows.json definition with SQL-based presence/patch detection, and a new entry in apps.json. Frontend changes add two SVG icon components and extend the software name-to-icon mapping. Changes
Related issues: None specified. Related PRs: None specified. Possibly related PRs
Suggested labels: Suggested reviewers: None specified. 🐰 Two new apps hop into the fold, ✨ Finishing Touches📝 Generate docstrings
🧪 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.
Pull request overview
Expands Fleet’s Windows Fleet-maintained apps (FMA) catalog by adding 3DF Zephyr Free and 4K Video Downloader+, including the required install/uninstall scripts, generated catalog outputs, and frontend software icons so the titles display with the correct branding in the UI.
Changes:
- Added new Windows FMA definitions (inputs) and published catalog entries (outputs) for 3DF Zephyr Free and 4K Video Downloader+.
- Introduced install/uninstall PowerShell scripts for both apps (including bundle/MSI uninstall handling for 4K Video Downloader+).
- Added new frontend icon components and mapped software titles to those icons.
Reviewed changes
Copilot reviewed 12 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/pages/SoftwarePage/components/icons/index.ts | Registers new icon components and maps software names to icons. |
| frontend/pages/SoftwarePage/components/icons/FourKVideoDownloaderPlus.tsx | Adds the 4K Video Downloader+ icon component. |
| frontend/pages/SoftwarePage/components/icons/3DfZephyrFree.tsx | Adds the 3DF Zephyr Free icon component. |
| ee/maintained-apps/outputs/apps.json | Adds the two apps to the published Windows maintained-apps catalog list. |
| ee/maintained-apps/outputs/4k-video-downloader-plus/windows.json | Publishes installer metadata + embedded scripts for 4K Video Downloader+. |
| ee/maintained-apps/outputs/3df-zephyr-free/windows.json | Publishes installer metadata + embedded scripts for 3DF Zephyr Free. |
| ee/maintained-apps/inputs/winget/scripts/4k-video-downloader-plus_uninstall.ps1 | Uninstall script that selects bundle vs MSI ARP entry and runs silent uninstall. |
| ee/maintained-apps/inputs/winget/scripts/4k-video-downloader-plus_install.ps1 | Silent installer script for the burn bundle. |
| ee/maintained-apps/inputs/winget/scripts/3df-zephyr-free_uninstall.ps1 | Uninstall script for versioned Inno Setup DisplayName. |
| ee/maintained-apps/inputs/winget/scripts/3df-zephyr-free_install.ps1 | Silent installer script for the Inno Setup EXE. |
| ee/maintained-apps/inputs/winget/4k-video-downloader-plus.json | Winget input definition for 4K Video Downloader+. |
| ee/maintained-apps/inputs/winget/3df-zephyr-free.json | Winget input definition for 3DF Zephyr Free (with fuzzy name matching). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ($exe -match '(?i)msiexec') { | ||
| if ($exeArgs -notmatch '(?i)/(x|uninstall)') { $exeArgs = "/X $exeArgs" } | ||
| if ($exeArgs -notmatch '(?i)/(qn|quiet)') { $exeArgs = "$exeArgs /qn" } | ||
| if ($exeArgs -notmatch '(?i)/norestart') { $exeArgs = "$exeArgs /norestart" } |
| } | ||
| ], | ||
| "refs": { | ||
| "9283b0c4": "# Uninstalls 4K Video Downloader+.\n#\n# The app is a WiX \"burn\" bundle that chains an MSI, and the ARP entry with\n# DisplayName \"4K Video Downloader+\" may be either the bundle (an .exe\n# UninstallString, needs /uninstall /quiet /norestart) or the chained MSI\n# (an MsiExec.exe /X{ProductCode} UninstallString, needs /qn /norestart --\n# NOT /uninstall, which is invalid for msiexec). Handle both shapes. The \"+\"\n# in the exact-match name keeps this from touching the separate MSI-based\n# \"4K Video Downloader\" product.\n\n$softwareName = \"4K Video Downloader+\"\n\n$machineKey = 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\nfunction Split-UninstallString {\n param([string]$raw)\n # Parse into executable + args, handling quoted/unquoted/bare shapes.\n if ($raw -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n return @($matches[1], $matches[2].Trim())\n } elseif ($raw -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n return @($matches[1], $matches[2].Trim())\n }\n return @($raw, \"\")\n}\n\n$exitCode = $null\n\ntry {\n\n[array]$uninstallKeys = Get-ChildItem `\n -Path @($machineKey, $machineKey32on64) `\n -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath }\n\n[array]$entries = $uninstallKeys | Where-Object { $_.DisplayName -eq $softwareName }\n\n# Prefer the burn bundle entry (non-msiexec .exe uninstaller): it removes the\n# whole chain, including the MSI. Fall back to the chained MSI entry.\n$bundle = $entries | Where-Object {\n $raw = if ($_.QuietUninstallString) { $_.QuietUninstallString } else { $_.UninstallString }\n $raw -and $raw -notmatch '(?i)msiexec'\n} | Select-Object -First 1\n$entry = if ($bundle) { $bundle } else { $entries | Select-Object -First 1 }\n\nif ($entry) {\n $raw = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }\n $exe, $exeArgs = Split-UninstallString -raw $raw\n\n if ($exe -match '(?i)msiexec') {\n if ($exeArgs -notmatch '(?i)/(x|uninstall)') { $exeArgs = \"/X $exeArgs\" }\n if ($exeArgs -notmatch '(?i)/(qn|quiet)') { $exeArgs = \"$exeArgs /qn\" }\n if ($exeArgs -notmatch '(?i)/norestart') { $exeArgs = \"$exeArgs /norestart\" }\n } else {\n if ($exeArgs -notmatch '/uninstall') { $exeArgs = \"/uninstall $exeArgs\" }\n if ($exeArgs -notmatch '/quiet') { $exeArgs = \"$exeArgs /quiet\" }\n if ($exeArgs -notmatch '/norestart') { $exeArgs = \"$exeArgs /norestart\" }\n }\n $exeArgs = $exeArgs.Trim()\n\n Write-Host \"Uninstall command: $exe\"\n Write-Host \"Uninstall args: $exeArgs\"\n $process = Start-Process -FilePath $exe -ArgumentList $exeArgs -NoNewWindow -PassThru -Wait\n $exitCode = $process.ExitCode\n}\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n\nif ($null -eq $exitCode) {\n Write-Host \"Uninstall entry not found for '$softwareName'.\"\n Exit 1\n}\n\nWrite-Host \"Uninstall exit code: $exitCode\"\n# 0 = success, 3010 = success but reboot required, 1641 = reboot initiated\nif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Exit 0 }\nExit $exitCode\n", |
**Related issue:** N/A — part of the Windows Fleet-maintained apps catalog expansion (letter B batch; follows #48872 and #48881). Adds six new Windows Fleet-maintained apps: | App | winget package | Installer | Notes | |-----|----------------|-----------|-------| | BandiView | `Bandisoft.BandiView` | EXE (NSIS-style), machine, x64 | Unversioned InstallerUrl → `ignore_hash` (URL verified to serve the binary reliably without Referer tricks). DisplayName "BandiView" stable across releases. | | BleachBit | `BleachBit.BleachBit` | EXE (NsisMultiUser), machine, x86 | Install script passes the case-sensitive `/allusers /S` — without `/allusers` the NsisMultiUser installer's scope is ambiguous. winget declares a VCRedist 2010 x86 dependency Fleet can't satisfy; noted as a caveat. | | Bulk Crap Uninstaller | `Klocman.BulkCrapUninstaller` | EXE (Inno), machine, x86 | ARP DisplayName is versioned ("BCUninstaller 6.2.0.0") and never matches the friendly name, so `unique_identifier` "BCUninstaller" + `fuzzy_match_name`. Registry version is 4-part vs winget's "6.2" — `version_compare` pads, verified consistent. | | BrowserStackLocal | `BrowserStack.BrowserStackLocal` | MSI (WiX), machine, x64 | Clean MSI (ALLUSERS=1, identity verified via msiinfo). Unversioned InstallerUrl → `ignore_hash`. | | Burp Suite Professional | `PortSwigger.BurpSuite.Professional` | EXE (install4j), machine, x64 | Mirrors the existing Burp Suite Community FMA: `-q -Dinstall4j.suppressUnattendedReboot=true` plus the load-bearing `-dir` into Program Files (install4j defaults to per-user otherwise). DisplayName is versioned **without** "Edition" ("Burp Suite Professional 2026.3.3"), unlike Community — fuzzy pattern `Burp Suite Professional %` can't collide with Community's. | | Bytello Share | `Bytello.BytelloShare` | EXE (NSIS), machine, x86 | Uses the nullsoft `agent=d` variant whose ARP identity ("Bytello Share" / publisher "Bytello Share") matches real-world inventory; the zip variant registers a different name ("BytelloShare") and its nested-MSI path is version-pinned and already stale. Vendor URL is a latest-pointer already ahead of winget → `ignore_hash`. **Note:** the input says `installer_type: "msi"` — the ingester classifies this nullsoft entry as msi because its URL has no file extension (vendor-type → URL-extension → machine-scope fallback chain in `ingester.go`); the custom scripts handle the actual NSIS exe. | Considered but **not** added (recorded in the workstream tracker): - **Bambu Studio** (`Bambulab.Bambustudio`): the uninstaller shows a keep-user-data confirmation dialog even with `/S` (deployment guides work around it with Send-Keys, impossible in a SYSTEM session) — same failure class that disqualified Adobe AIR in the letter A batch. - **Bridge Designer** (`StephenRessler.BridgeDesigner`): installer URL 404s (file removed from SourceForge) and the desktop product was discontinued July 1, 2026 in favor of a browser-based edition. - **BurnAware Free** (`Burnaware.BurnAwareFree`): the vendor deletes each old release URL — the winget-pinned installer already redirects to their homepage, so pinned downloads break every release cycle. Registry identities were verified per app (msiinfo Property tables for MSIs; vendor installer sources, winget AppsAndFeaturesEntries, and uninstall-database corroboration for EXEs). Installer SHAs verified against manifests where URLs are version-pinned; unversioned URLs use `ignore_hash` per the TeamViewer/Chrome precedent. Icons generated via `tools/software/icons/generate-icons.sh`. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops ## Testing - [ ] QA'd all new/changed functionality manually (relying on the FMA CI validator for Windows install/uninstall validation)
Related issue: N/A — part of the Windows Fleet-maintained apps catalog expansion.
Adds two new Windows Fleet-maintained apps (the "digit" batch of the Windows FMA workstream):
OpenMedia.4KVideoDownloaderPlusDisplayName"4K Video Downloader+",Publisher"InterPromo GMBH" (differs from the winget locale publisher "Open Media LLC", soprogram_publisheris set). Registers TWO ARP entries with the same DisplayName (the bundle and its chained MSI). The uninstall script prefers the bundle entry and normalizes msiexec args when only the MSI entry is present (validator-confirmed). Exact DisplayName matching keeps it from touching the non-plus product.3Dflow.3DFZephyr.Freefuzzy_match_name: true. The paid edition registers as "3DF Zephyr version X" (no "Free") and is not matched. Standard Inno silent switches.Also considered from this batch but not added:
OpenMedia.4KVideoDownloader, the classic 4.x app): verified and validated successfully, but intentionally dropped — it's in maintenance mode and 4K Video Downloader+ is the actively developed successor, so we're offering only the + app.3Dconnexion.3DxWare.10): the vendor bootstrapper hung for 10 minutes and exited 1 with no output in the validator's headless SYSTEM session (driver install), so it was dropped after the first validation run.3CX.Softphone): MSIX with an unversionedInstallerUrl(.../3CX.msix) — the file at that URL is already a newer build (20.0.1162.0) than the manifest's pinned version/SHA (20.0.1102.0), so installs would fail hash validation. Can be revisited withignore_hashplus Windows-App-style MSIX provisioning scripts.Installer SHAs in the outputs were verified against the winget manifests. Icons generated via
tools/software/icons/generate-icons.sh; icon component names were adjusted to valid JS identifiers (ThreeDfZephyrFree,FourKVideoDownloaderPlus) following theZeroOneZeroEditor/FourK*precedent.Checklist for submitter
SELECT *is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.Testing
Summary by CodeRabbit
New Features
Bug Fixes