Windows FMA - Windsurf (#46401)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Windows support for Windsurf as a managed app (version 2.3.15) with packaged manifest and installer metadata. * Added automated silent install and uninstall flows for Windows. * **Bug Fixes** * Improved post-install/uninstall process handling to avoid file-locks. * Enhanced uninstall detection and exit-code handling to better report success/failure. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/46401?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Allen Houchins <allenhouchins@mac.com>
This commit is contained in:
co-authored by
Allen Houchins
parent
2ba95035e1
commit
a93ea6974d
@@ -0,0 +1,44 @@
|
||||
|
||||
# install switches:
|
||||
# /VERYSILENT = no UI at all
|
||||
# /SUPPRESSMSGBOXES = suppress every message box
|
||||
# /NORESTART = don't reboot (returns 3010 instead, treat as success)
|
||||
# /ALLUSERS = machine-scope install. Requires elevation; the
|
||||
# installer self-elevates (manifest:
|
||||
# ElevationRequirement: elevatesSelf), and Fleet/
|
||||
# SYSTEM already runs elevated anyway.
|
||||
# /MERGETASKS=!runcode = keep default selectable tasks but deselect
|
||||
# "runcode" (which auto-launches Windsurf after
|
||||
# install). Inno's "!" prefix means "deselect".
|
||||
|
||||
$exeFilePath = "${env:INSTALLER_PATH}"
|
||||
|
||||
# 0 = success; 3010 = success but reboot required.
|
||||
$ExpectedExitCodes = @(0, 3010)
|
||||
|
||||
try {
|
||||
$processOptions = @{
|
||||
FilePath = "$exeFilePath"
|
||||
ArgumentList = "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART",
|
||||
"/ALLUSERS", "/MERGETASKS=!runcode"
|
||||
PassThru = $true
|
||||
Wait = $true
|
||||
}
|
||||
|
||||
$process = Start-Process @processOptions
|
||||
$exitCode = $process.ExitCode
|
||||
Write-Host "Install exit code: $exitCode"
|
||||
|
||||
# Defensive: even with !runcode, Inno installers occasionally launch the
|
||||
# app via post-install scripts. Stop Windsurf if it slipped through so
|
||||
# files aren't locked for later detection/uninstall.
|
||||
Start-Sleep -Seconds 5
|
||||
Stop-Process -Name "Windsurf" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if ($ExpectedExitCodes -contains $exitCode) { Exit 0 }
|
||||
Exit $exitCode
|
||||
|
||||
} catch {
|
||||
Write-Host "Error: $_"
|
||||
Exit 1
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
$softwareNameLike = "Windsurf*"
|
||||
$publisherLike = "*Codeium*"
|
||||
|
||||
$paths = @(
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
|
||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
|
||||
)
|
||||
|
||||
$ExpectedExitCodes = @(0, 3010, 1641)
|
||||
$exitCode = 0
|
||||
|
||||
try {
|
||||
|
||||
[array]$uninstallKeys = Get-ChildItem `
|
||||
-Path $paths `
|
||||
-ErrorAction SilentlyContinue |
|
||||
ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }
|
||||
|
||||
$selected = $null
|
||||
foreach ($key in $uninstallKeys) {
|
||||
if ($key.DisplayName -and $key.DisplayName -like $softwareNameLike -and $key.Publisher -like $publisherLike) {
|
||||
$selected = $key
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $selected -or (-not $selected.UninstallString -and -not $selected.QuietUninstallString)) {
|
||||
Write-Host "Uninstall entry not found for $softwareNameLike"
|
||||
Exit 0
|
||||
}
|
||||
|
||||
# Stop Windsurf and any helpers running out of the install dir so the
|
||||
# uninstaller doesn't fail on locked files.
|
||||
Stop-Process -Name "Windsurf" -Force -ErrorAction SilentlyContinue
|
||||
if ($selected.InstallLocation -and (Test-Path -LiteralPath $selected.InstallLocation)) {
|
||||
$loc = $selected.InstallLocation.TrimEnd('\')
|
||||
Get-Process | Where-Object { $_.Path -and $_.Path -like "$loc\*" } |
|
||||
ForEach-Object { Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Prefer QuietUninstallString -- Inno populates it with /VERYSILENT
|
||||
# /SUPPRESSMSGBOXES already, so we just add /NORESTART if missing.
|
||||
$uninstallCommand = if ($selected.QuietUninstallString) {
|
||||
$selected.QuietUninstallString
|
||||
} else {
|
||||
$selected.UninstallString
|
||||
}
|
||||
|
||||
# Parse the uninstaller exe path. Inno usually quotes it because the install
|
||||
# path often contains "Program Files".
|
||||
$exePath = ""
|
||||
$existingArgs = ""
|
||||
if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
|
||||
$exePath = $matches[1]
|
||||
$existingArgs = $matches[2].Trim()
|
||||
} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
|
||||
$exePath = $matches[1]
|
||||
$existingArgs = $matches[2].Trim()
|
||||
} else {
|
||||
Throw "Could not parse uninstall string: $uninstallCommand"
|
||||
}
|
||||
|
||||
|
||||
$argumentList = @()
|
||||
if ($existingArgs) { $argumentList += ($existingArgs -split '\s+') }
|
||||
foreach ($s in @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART")) {
|
||||
if ($argumentList -notcontains $s) { $argumentList += $s }
|
||||
}
|
||||
|
||||
Write-Host "Selected entry DisplayName: $($selected.DisplayName)"
|
||||
Write-Host "Uninstall command: $exePath"
|
||||
Write-Host "Uninstall args: $($argumentList -join ' ')"
|
||||
|
||||
$processOptions = @{
|
||||
FilePath = $exePath
|
||||
ArgumentList = $argumentList
|
||||
PassThru = $true
|
||||
Wait = $true
|
||||
}
|
||||
|
||||
$process = Start-Process @processOptions
|
||||
$exitCode = $process.ExitCode
|
||||
Write-Host "Uninstall exit code: $exitCode"
|
||||
|
||||
if ($ExpectedExitCodes -contains $exitCode) { Exit 0 }
|
||||
Exit $exitCode
|
||||
|
||||
} catch {
|
||||
Write-Host "Error: $_"
|
||||
Exit 1
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Windsurf",
|
||||
"slug": "windsurf/windows",
|
||||
"package_identifier": "Codeium.Windsurf",
|
||||
"unique_identifier": "Windsurf",
|
||||
"install_script_path": "ee/maintained-apps/inputs/winget/scripts/windsurf_install.ps1",
|
||||
"uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/windsurf_uninstall.ps1",
|
||||
"installer_arch": "x64",
|
||||
"installer_type": "exe",
|
||||
"installer_scope": "machine",
|
||||
"default_categories": ["Developer tools"]
|
||||
}
|
||||
@@ -2423,6 +2423,13 @@
|
||||
"unique_identifier": "com.exafunction.windsurf",
|
||||
"description": "Windsurf is an agentic IDE powered by AI Flow paradigm."
|
||||
},
|
||||
{
|
||||
"name": "Windsurf",
|
||||
"slug": "windsurf/windows",
|
||||
"platform": "windows",
|
||||
"unique_identifier": "Windsurf",
|
||||
"description": "Windsurf is an agentic IDE powered by AI Flow paradigm."
|
||||
},
|
||||
{
|
||||
"name": "WinSCP",
|
||||
"slug": "winscp/windows",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.3.15",
|
||||
"queries": {
|
||||
"exists": "SELECT 1 FROM programs WHERE name = 'Windsurf' AND publisher = 'Codeium';",
|
||||
"patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name = 'Windsurf' AND publisher = 'Codeium' AND version_compare(version, '2.3.15') < 0);"
|
||||
},
|
||||
"installer_url": "https://windsurf-stable.codeiumdata.com/win32-x64/stable/c46c49e94b4d3f41181204d59809d8f1b2c48d68/WindsurfSetup-x64-2.3.15.exe",
|
||||
"install_script_ref": "232abd06",
|
||||
"uninstall_script_ref": "bb230cc3",
|
||||
"sha256": "d30ff9c6a490597aac354746b689335587f6bf8da671b935e8d1be51b3825d15",
|
||||
"default_categories": [
|
||||
"Developer tools"
|
||||
]
|
||||
}
|
||||
],
|
||||
"refs": {
|
||||
"232abd06": "\n# install switches:\n# /VERYSILENT = no UI at all\n# /SUPPRESSMSGBOXES = suppress every message box\n# /NORESTART = don't reboot (returns 3010 instead, treat as success)\n# /ALLUSERS = machine-scope install. Requires elevation; the\n# installer self-elevates (manifest:\n# ElevationRequirement: elevatesSelf), and Fleet/\n# SYSTEM already runs elevated anyway.\n# /MERGETASKS=!runcode = keep default selectable tasks but deselect\n# \"runcode\" (which auto-launches Windsurf after\n# install). Inno's \"!\" prefix means \"deselect\".\n\n$exeFilePath = \"${env:INSTALLER_PATH}\"\n\n# 0 = success; 3010 = success but reboot required.\n$ExpectedExitCodes = @(0, 3010)\n\ntry {\n $processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"/VERYSILENT\", \"/SUPPRESSMSGBOXES\", \"/NORESTART\",\n \"/ALLUSERS\", \"/MERGETASKS=!runcode\"\n PassThru = $true\n Wait = $true\n }\n\n $process = Start-Process @processOptions\n $exitCode = $process.ExitCode\n Write-Host \"Install exit code: $exitCode\"\n\n # Defensive: even with !runcode, Inno installers occasionally launch the\n # app via post-install scripts. Stop Windsurf if it slipped through so\n # files aren't locked for later detection/uninstall.\n Start-Sleep -Seconds 5\n Stop-Process -Name \"Windsurf\" -Force -ErrorAction SilentlyContinue\n\n if ($ExpectedExitCodes -contains $exitCode) { Exit 0 }\n Exit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n",
|
||||
"bb230cc3": "$softwareNameLike = \"Windsurf*\"\n$publisherLike = \"*Codeium*\"\n\n$paths = @(\n 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',\n 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',\n 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall'\n)\n\n$ExpectedExitCodes = @(0, 3010, 1641)\n$exitCode = 0\n\ntry {\n\n[array]$uninstallKeys = Get-ChildItem `\n -Path $paths `\n -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }\n\n$selected = $null\nforeach ($key in $uninstallKeys) {\n if ($key.DisplayName -and $key.DisplayName -like $softwareNameLike -and $key.Publisher -like $publisherLike) {\n $selected = $key\n break\n }\n}\n\nif (-not $selected -or (-not $selected.UninstallString -and -not $selected.QuietUninstallString)) {\n Write-Host \"Uninstall entry not found for $softwareNameLike\"\n Exit 0\n}\n\n# Stop Windsurf and any helpers running out of the install dir so the\n# uninstaller doesn't fail on locked files.\nStop-Process -Name \"Windsurf\" -Force -ErrorAction SilentlyContinue\nif ($selected.InstallLocation -and (Test-Path -LiteralPath $selected.InstallLocation)) {\n $loc = $selected.InstallLocation.TrimEnd('\\')\n Get-Process | Where-Object { $_.Path -and $_.Path -like \"$loc\\*\" } |\n ForEach-Object { Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue }\n}\nStart-Sleep -Seconds 2\n\n# Prefer QuietUninstallString -- Inno populates it with /VERYSILENT\n# /SUPPRESSMSGBOXES already, so we just add /NORESTART if missing.\n$uninstallCommand = if ($selected.QuietUninstallString) {\n $selected.QuietUninstallString\n} else {\n $selected.UninstallString\n}\n\n# Parse the uninstaller exe path. Inno usually quotes it because the install\n# path often contains \"Program Files\".\n$exePath = \"\"\n$existingArgs = \"\"\nif ($uninstallCommand -match '^\\s*\"([^\"]+)\"\\s*(.*)$') {\n $exePath = $matches[1]\n $existingArgs = $matches[2].Trim()\n} elseif ($uninstallCommand -match '(?i)^\\s*(.+?\\.exe)\\s*(.*)$') {\n $exePath = $matches[1]\n $existingArgs = $matches[2].Trim()\n} else {\n Throw \"Could not parse uninstall string: $uninstallCommand\"\n}\n\n\n$argumentList = @()\nif ($existingArgs) { $argumentList += ($existingArgs -split '\\s+') }\nforeach ($s in @(\"/VERYSILENT\", \"/SUPPRESSMSGBOXES\", \"/NORESTART\")) {\n if ($argumentList -notcontains $s) { $argumentList += $s }\n}\n\nWrite-Host \"Selected entry DisplayName: $($selected.DisplayName)\"\nWrite-Host \"Uninstall command: $exePath\"\nWrite-Host \"Uninstall args: $($argumentList -join ' ')\"\n\n$processOptions = @{\n FilePath = $exePath\n ArgumentList = $argumentList\n PassThru = $true\n Wait = $true\n}\n\n$process = Start-Process @processOptions\n$exitCode = $process.ExitCode\nWrite-Host \"Uninstall exit code: $exitCode\"\n\nif ($ExpectedExitCodes -contains $exitCode) { Exit 0 }\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user