feat(windows): add GUI uninstaller script

Adds a Windows-specific uninstaller under windows/uninstaller/ that:
- Shows a Yes/No confirmation dialog via Windows Forms
- Displays a live progress window (no console) during removal
- Removes app files, Start Menu/Desktop/Startup shortcuts,
  AppData configuration, and all registry entries
- Mirrors paths/keys from the Inno Setup installer (inno_setup.iss)
- Includes a Register-SpotubeUninstaller.ps1 helper to surface the
  uninstaller in Control Panel -> Programs and Features
- Provides a .bat wrapper (WindowStyle Hidden) so only GUI dialogs show

Closes: N/A
This commit is contained in:
Shreyas D R 2026-04-03 00:14:03 +05:30
parent 2cc8d2620d
commit 4b09d2133d
3 changed files with 292 additions and 0 deletions

View File

@ -0,0 +1,85 @@
<#
.SYNOPSIS
Registers the Spotube custom uninstaller in Windows Programs and Features.
.DESCRIPTION
Writes a registry entry to HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
so that Spotube appears in Control Panel -> Programs and Features and in
Settings -> Apps -> Installed Apps. The UninstallString points to Uninstall-Spotube.bat
which must be in the same directory as this script.
.NOTES
Requires: Uninstall-Spotube.bat in the same folder.
Run as Administrator.
#>
param()
# Re-elevate if not already running as Administrator.
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator
)
if (-not $isAdmin) {
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
exit
}
$scriptDir = Split-Path -Parent $PSCommandPath
$uninstallBat = Join-Path $scriptDir "Uninstall-Spotube.bat"
if (-Not (Test-Path $uninstallBat)) {
Write-Error "Uninstall-Spotube.bat not found in '$scriptDir'. Ensure all uninstaller files are kept together."
pause
exit 1
}
# Version sourced from pubspec.yaml (5.1.1+44). Override if the installed exe exposes a FileVersion.
$displayVersion = "5.1.1"
$iconPath = ""
foreach ($candidate in @(
(Join-Path $env:ProgramFiles "Spotube\spotube.exe"),
(Join-Path $env:LOCALAPPDATA "Programs\Spotube\spotube.exe")
)) {
if (Test-Path $candidate) {
$fileVer = (Get-Item $candidate).VersionInfo.FileVersion
if ($fileVer) { $displayVersion = $fileVer }
$iconPath = $candidate
break
}
}
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SpotubeCustomUninstaller"
if (-Not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}
# Registry values follow the standard Windows "Add/Remove Programs" schema.
$stringValues = @{
DisplayName = "Spotube"
DisplayVersion = $displayVersion
Publisher = "KRTirtho, OSS"
URLInfoAbout = "https://github.com/KRTirtho/spotube"
HelpLink = "https://github.com/KRTirtho/spotube/issues"
UninstallString = "`"$uninstallBat`""
InstallDate = (Get-Date -Format "yyyyMMdd")
}
$dwordValues = @{
NoModify = 1
NoRepair = 1
EstimatedSize = 80000 # Approximate installed size in KB
}
foreach ($kv in $stringValues.GetEnumerator()) {
Set-ItemProperty -Path $regPath -Name $kv.Key -Value $kv.Value
}
foreach ($kv in $dwordValues.GetEnumerator()) {
Set-ItemProperty -Path $regPath -Name $kv.Key -Value $kv.Value -Type DWord
}
if ($iconPath) {
Set-ItemProperty -Path $regPath -Name "DisplayIcon" -Value "$iconPath,0"
}
Write-Host ""
Write-Host "Spotube registered in Programs and Features." -ForegroundColor Green
Write-Host "Open Control Panel -> Programs and Features (or Settings -> Apps) to uninstall."
Write-Host ""
pause

View File

@ -0,0 +1,4 @@
@echo off
:: Launches the Spotube GUI uninstaller without showing a console window.
:: -WindowStyle Hidden ensures only the WinForms dialogs are visible.
powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0Uninstall-Spotube.ps1"

View File

@ -0,0 +1,203 @@
<#
.SYNOPSIS
GUI-based uninstaller for Spotube on Windows.
.DESCRIPTION
Removes all Spotube installation artifacts: application files, shortcuts,
user data, and registry entries. Mirrors what the Inno Setup installer
creates (inno_setup.iss / make_config.yaml).
.PARAMETER Silent
Suppresses all GUI dialogs and runs non-interactively.
#>
param (
[switch]$Silent
)
# Application metadata — must match Inno Setup make_config.yaml
$APP_NAME = "Spotube"
$APP_ID = "80B901C8-D6FE-494E-8AF7-A2BD440E8644"
$INNO_REG_KEY = "${APP_ID}_is1"
$CUSTOM_REG_KEY = "SpotubeCustomUninstaller"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Displays a standard Windows message box and returns the DialogResult.
function Show-MessageBox {
param(
[string]$Message,
[string]$Title = "Spotube Uninstaller",
[System.Windows.Forms.MessageBoxButtons]$Buttons = [System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]$Icon = [System.Windows.Forms.MessageBoxIcon]::Information
)
return [System.Windows.Forms.MessageBox]::Show($Message, $Title, $Buttons, $Icon)
}
# Creates and displays the progress window. Returns a hashtable of controls
# so the caller can update label text, sub-label, and progress bar value.
function Show-ProgressForm {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Spotube Uninstaller"
$form.Size = New-Object System.Drawing.Size(460, 200)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$form.ControlBox = $false # Disable X button during active uninstall
$form.TopMost = $true
# Use the installed Spotube executable's icon if available
foreach ($candidate in @(
(Join-Path $env:ProgramFiles "Spotube\spotube.exe"),
(Join-Path $env:LOCALAPPDATA "Programs\Spotube\spotube.exe")
)) {
if (Test-Path $candidate) {
try { $form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($candidate) } catch {}
break
}
}
$lblStatus = New-Object System.Windows.Forms.Label
$lblStatus.Location = New-Object System.Drawing.Point(16, 16)
$lblStatus.Size = New-Object System.Drawing.Size(420, 20)
$lblStatus.Text = "Preparing..."
$lblStatus.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$form.Controls.Add($lblStatus)
$progressBar = New-Object System.Windows.Forms.ProgressBar
$progressBar.Location = New-Object System.Drawing.Point(16, 48)
$progressBar.Size = New-Object System.Drawing.Size(420, 22)
$progressBar.Minimum = 0
$progressBar.Maximum = 100
$progressBar.Value = 0
$form.Controls.Add($progressBar)
$lblDetail = New-Object System.Windows.Forms.Label
$lblDetail.Location = New-Object System.Drawing.Point(16, 80)
$lblDetail.Size = New-Object System.Drawing.Size(420, 16)
$lblDetail.Text = ""
$lblDetail.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$lblDetail.ForeColor = [System.Drawing.Color]::DimGray
$form.Controls.Add($lblDetail)
$form.Show()
$form.Refresh()
return @{
Form = $form
Status = $lblStatus
Detail = $lblDetail
Progress = $progressBar
}
}
# Re-elevate as Administrator if the script is not already running elevated.
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator
)
if (-not $isAdmin) {
$relaunchArgs = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`""
if ($Silent) { $relaunchArgs += " -Silent" }
Start-Process powershell -ArgumentList $relaunchArgs -Verb RunAs
exit
}
# Prompt the user before making any changes.
if (-not $Silent) {
$confirmMsg = @"
Are you sure you want to uninstall Spotube?
This will permanently remove:
Application files
Desktop, Start Menu, and Startup shortcuts
User configuration and cached data
Windows registry entries
"@
$result = Show-MessageBox -Message $confirmMsg -Title "Uninstall Spotube" `
-Buttons ([System.Windows.Forms.MessageBoxButtons]::YesNo) `
-Icon ([System.Windows.Forms.MessageBoxIcon]::Question)
if ($result -ne [System.Windows.Forms.DialogResult]::Yes) {
Show-MessageBox -Message "Uninstallation was cancelled. No changes were made." `
-Title "Cancelled" -Icon ([System.Windows.Forms.MessageBoxIcon]::Information)
exit
}
}
$ui = Show-ProgressForm
# Updates the progress window with new status text and a percentage value.
function Update-Progress ([string]$status, [string]$detail, [int]$percent) {
$ui.Status.Text = $status
$ui.Detail.Text = $detail
$ui.Progress.Value = [Math]::Min($percent, 100)
$ui.Form.Refresh()
}
# Silently removes a file or directory; no-ops if the path does not exist.
function Safe-Remove ([string]$path) {
if ($path -and (Test-Path $path)) {
try { Remove-Item -Path $path -Recurse -Force -ErrorAction Stop } catch {}
}
}
# Step 1: Terminate any running Spotube processes before touching files.
Update-Progress "Stopping Spotube..." "Terminating running processes" 5
Get-Process -Name "spotube" -ErrorAction SilentlyContinue |
Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 1500
# Step 2: Delete the installation directory.
# Inno Setup installs to {autopf}\Spotube — covers both admin and per-user installs.
Update-Progress "Removing application files..." "Deleting installation directory" 20
@(
(Join-Path $env:ProgramFiles $APP_NAME),
(Join-Path $env:ProgramW6432 $APP_NAME),
(Join-Path $env:LOCALAPPDATA "Programs\$APP_NAME")
) | Select-Object -Unique | ForEach-Object { Safe-Remove $_ }
# Step 3: Remove shortcuts created by the Inno Setup [Icons] section.
# Covers: {autoprograms} (Start Menu), {autodesktop} (Desktop), {userstartup} (Startup).
Update-Progress "Removing shortcuts..." "Cleaning Start Menu, Desktop, and Startup" 40
@(
(Join-Path $env:ProgramData "Microsoft\Windows\Start Menu\Programs\$APP_NAME.lnk"),
(Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\$APP_NAME.lnk"),
(Join-Path $env:ProgramData "Microsoft\Windows\Start Menu\Programs\$APP_NAME"),
(Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\$APP_NAME"),
(Join-Path ([Environment]::GetFolderPath("CommonDesktopDirectory")) "$APP_NAME.lnk"),
(Join-Path ([Environment]::GetFolderPath("Desktop")) "$APP_NAME.lnk"),
(Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\Startup\$APP_NAME.lnk")
) | ForEach-Object { Safe-Remove $_ }
# Step 4: Remove Flutter application data stored in AppData.
Update-Progress "Removing user data..." "Clearing configuration and cache" 60
@(
(Join-Path $env:APPDATA "com.krtirtho.spotube"),
(Join-Path $env:LOCALAPPDATA "com.krtirtho.spotube"),
(Join-Path $env:APPDATA "spotube"),
(Join-Path $env:LOCALAPPDATA "spotube")
) | ForEach-Object { Safe-Remove $_ }
# Step 5: Remove all Windows registry entries.
# Targets the Inno Setup key ({AppId}_is1) and our custom Control Panel key.
Update-Progress "Removing registry entries..." "Cleaning uninstall records" 80
foreach ($root in @("HKLM:\SOFTWARE", "HKCU:\SOFTWARE", "HKLM:\SOFTWARE\WOW6432Node")) {
foreach ($key in @($INNO_REG_KEY, $CUSTOM_REG_KEY)) {
Safe-Remove "$root\Microsoft\Windows\CurrentVersion\Uninstall\$key"
}
}
Safe-Remove "HKCU:\SOFTWARE\KRTirtho"
Safe-Remove "HKCU:\SOFTWARE\Spotube"
Update-Progress "Complete" "Spotube has been removed." 100
Start-Sleep -Milliseconds 500
$ui.Form.Close()
$ui.Form.Dispose()
# Show the final confirmation dialog with an OK button to dismiss.
if (-not $Silent) {
Show-MessageBox `
-Message "Spotube has been successfully uninstalled.`n`nClick OK to close." `
-Title "Uninstall Complete" `
-Buttons ([System.Windows.Forms.MessageBoxButtons]::OK) `
-Icon ([System.Windows.Forms.MessageBoxIcon]::Information)
}