<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** #50020 # What this does Adds **Rtools** as a Windows Fleet-maintained app. One of the 11 apps split out of #48501 that failed the FMA validator; #50016 shipped the 6 that passed. ## Why it was failing The install script hit the validator's 10-minute `executeScript` cap exactly: ``` 20:41:22 INFO msg="Executing install script..." app=Rtools 20:51:22 ERROR msg="Error executing install script: exit status 1" # exactly 10:00 later 20:51:22 WARN msg="failed to remove rtools45-6768-6492.exe: ... Access is denied." ``` The locked installer in the temp dir shows a process was still alive. `Start-Process -Wait` waits for the process *and all of its descendants*, which is the same root cause as the other install-timeout apps in this batch. Rtools is also the one app in the batch where a **slow unpack** is a plausible second cause — the installer is ~460 MB and expands a full toolchain. So rather than assume, the script now waits on the installer process alone with a 480s cap (under the caller's 10-minute budget) and logs elapsed time plus Add/Remove Programs registration state on every poll. If the cap is reached: - **registered** → the install finished and only a lingering child remains, so it stops that process and succeeds; - **not registered** → the unpack genuinely didn't finish, and it fails with that stated explicitly. Either way the CI log now says which one happened instead of just timing out. ## Notes - **Identity verified against the installer**, not winget metadata. The setup stub's PE version resource reads `CompanyName: The R Foundation`, `ProductName: Rtools`. Inno derives `VersionInfoCompany` from `AppPublisher`, so the ARP publisher is `The R Foundation` — which is what the exists query uses. - **Versioned ARP name.** The registry `DisplayName` is `Rtools 4.5 (6768-6492)`, so the input uses `fuzzy_match_name` and the exists query is `name LIKE 'Rtools %'`. - Installs to `C:\rtools45`, not Program Files, so the validator's "no changes detected in `C:\Program Files`" line is an expected warning, not a failure. - Ships a new catalog icon and website asset. # 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 - [x] FMA CI validator (install → detect → uninstall) **passes** on the SYSTEM-context Windows runner — [run 30384196159](https://github.com/fleetdm/fleet/actions/runs/30384196159) (`All checks passed`) - [x] Generated output verified locally: manifest SHA matches the winget manifest, exists/patched queries checked against the installer's PE version resource, `apps.json` is valid JSON with a description filled in. - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Rtools as a supported Windows application. * Added installation and uninstallation support with silent setup and silent removal. * Added Rtools version metadata, installer verification, and Developer tools categorization. * Added a dedicated Rtools icon for software listings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
37 lines
2.0 KiB
PowerShell
37 lines
2.0 KiB
PowerShell
# The ARP DisplayName carries a version and build ("Rtools 4.5 (6768-6492)"), so
|
|
# match on a prefix, plus the publisher to avoid other products sharing it.
|
|
$softwareName = "Rtools"
|
|
$softwareNameLike = "$softwareName*"
|
|
$softwarePublisher = "The R Foundation"
|
|
$uninstallArgs = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
|
|
|
|
$machineKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
|
$machineKey32on64 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
|
$exitCode = 0
|
|
|
|
try {
|
|
[array]$uninstallKeys = Get-ChildItem -Path @($machineKey, $machineKey32on64) -ErrorAction SilentlyContinue |
|
|
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }
|
|
|
|
$foundUninstaller = $false
|
|
foreach ($key in $uninstallKeys) {
|
|
if ($key.DisplayName -like $softwareNameLike -and $key.Publisher -eq $softwarePublisher) {
|
|
$foundUninstaller = $true
|
|
$uninstallCommand = if ($key.QuietUninstallString) { $key.QuietUninstallString } else { $key.UninstallString }
|
|
if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
|
|
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
|
|
} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
|
|
$uninstallCommand = $Matches[1]; if ($Matches[2]) { $uninstallArgs = "$($Matches[2]) $uninstallArgs".Trim() }
|
|
}
|
|
Write-Host "Uninstall command: $uninstallCommand"; Write-Host "Uninstall args: $uninstallArgs"
|
|
$processOptions = @{ FilePath = $uninstallCommand; PassThru = $true; Wait = $true }
|
|
if ($uninstallArgs -ne '') { $processOptions.ArgumentList = $uninstallArgs }
|
|
$process = Start-Process @processOptions
|
|
$exitCode = $process.ExitCode; Write-Host "Uninstall exit code: $exitCode"; break
|
|
}
|
|
}
|
|
if (-not $foundUninstaller) { Write-Host "Uninstall entry not found for '$softwareName'."; Exit 0 }
|
|
} catch { Write-Host "Error: $_"; Exit 1 }
|
|
|
|
Exit $exitCode
|