Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions ee/maintained-apps/inputs/winget/scripts/windsurf_install.ps1
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 93 additions & 0 deletions ee/maintained-apps/inputs/winget/scripts/windsurf_uninstall.ps1
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions ee/maintained-apps/inputs/winget/windsurf.json
Original file line number Diff line number Diff line change
@@ -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"]
}
7 changes: 7 additions & 0 deletions ee/maintained-apps/outputs/apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,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",
Expand Down
22 changes: 22 additions & 0 deletions ee/maintained-apps/outputs/windsurf/windows.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading