DBA Scripts: Uninstall SSMS via PowerShell

🛠️Part of the SSMS Complete Guide, installing, configuring and fixing SQL Server Management Studio.In: Server & Configuration › Installation & Patching

When the Uninstaller Fails

SSMS has to come off a machine, and Settings → Apps failed, left it half removed, or is no use across a fleet. This script finds every SSMS install in the registry and removes it. When one method fails, it tries the next. To do it by hand instead, see the manual SSMS uninstall walkthrough.

What it tries, in order:

  • SSMS 21 and later: the Visual Studio Installer’s uninstall command, with the product and channel IDs from vswhere. If that fails, the uninstall command stored in the registry. If that fails, winget.
  • SSMS 17 to 20: the WiX uninstaller with /uninstall /quiet /norestart. If that fails, winget. Last, a direct MsiExec /X by product code.

If all of it fails, the script logs “Uninstall failed - remove manually via Settings -> Apps”. Do that next. If SSMS 21 or 22 still will not go, MS Docs has one last cleanup tool, covered in the FAQ below.


Why the Fallbacks Matter

  • One method failing does not mean manual cleanup. A corrupted Package Cache, a stale registry entry or a vswhere mismatch is what the next method is there to get round.
  • Most of what it runs is logged. Each Visual Studio Installer command line goes to the log, and on SSMS 17 to 20 the success line names the method that worked. The winget attempts are not logged, and on SSMS 21 and later the success line does not name the method.
  • It checks the result. After the uninstall it looks in the registry again for the entry it targeted, before it calls the job done.

When to Run This Script

  • Clearing an older SSMS before Install and Update SSMS via PowerShell, which stops when it finds one. That stop is the script’s choice. Per MS Docs, SSMS 22 installs side by side with an earlier major version.
  • Decommissioning a machine, or removing SSMS from a box that no longer needs it.
  • Clearing a corrupted or partial install that Settings → Apps will not remove. On SSMS 17 to 20 the last method removes the MSI by product code, skipping the bootstrapper.

The Script

# uninstall-ssms.ps1 - Silently uninstall SQL Server Management Studio
# RiskLevel   : MEDIUM - removes SSMS from this machine; does not touch the database engine
#
# SSMS 17-20  : WiX installer - reads UninstallString, appends /uninstall /quiet /norestart
# SSMS 21+    : VS Installer  - uses vswhere to find productId, runs setup.exe uninstall -q
#               winget is tried as a fallback if vswhere fails
#
# Parameters:
#   -WhatIf   : show what would run, no changes
#   -Force    : skip confirmation prompts
#   -Passive  : show VS Installer progress window (default is silent with console timer)
#
# Examples:
#   .\uninstall-ssms.ps1 -WhatIf
#   .\uninstall-ssms.ps1
#   .\uninstall-ssms.ps1 -Force
param(
    [switch]$WhatIf,
    [switch]$Force,
    [switch]$Passive
)

$ErrorActionPreference = 'Stop'

# -- Pending reboot check ------------------------------------------------------
$rebootKeys = @(
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired',
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending',
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress'
)
$rebootPending = ($rebootKeys | Where-Object { Test-Path $_ }) -or
    (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations

if ($rebootPending -and -not $WhatIf) {
    Write-Host 'NOTE: A system reboot is pending on this machine.' -ForegroundColor Yellow
}

# -- Admin check ---------------------------------------------------------------
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
    [Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin -and -not $WhatIf) {
    Write-Host 'ERROR: This script must be run as Administrator (or use -WhatIf to preview without uninstalling).' -ForegroundColor Red; exit 1
}

# -- Logging -------------------------------------------------------------------
# Falls back to a logs\ folder next to the script when run standalone (not in the repo)
$_repoOutputDir = Join-Path $PSScriptRoot '..\..\..\output-files'
$logDir = [System.IO.Path]::GetFullPath($(if (Test-Path $_repoOutputDir) {
    Join-Path $_repoOutputDir 'patches\ssms'
} else {
    Join-Path $PSScriptRoot 'logs'
}))
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
$ts       = Get-Date -Format 'yyyyMMdd-HHmmss'
$logFile  = Join-Path $logDir "ssms-uninstall-$ts.log"

function Write-DbaLog {
    param([string]$Msg, [string]$Color = 'White')
    $line = "[$(Get-Date -Format 'HH:mm:ss')] $Msg"
    Write-Host $line -ForegroundColor $Color
    Add-Content -Path $logFile -Value $line
}

Write-DbaLog "SSMS uninstall log - $ts" 'Cyan'

# -- Find all installed SSMS entries ------------------------------------------
$regPaths = @(
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
    'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)

$ssmsEntries = @(
    foreach ($p in $regPaths) {
        Get-ItemProperty $p -ErrorAction SilentlyContinue |
            Where-Object {
                $_.DisplayName -like '*SQL Server Management Studio*' -and
                # Skip MSI sub-component registrations (Language Pack, base MSI entries).
                # UninstallString starting with 'MsiExec.exe /I{' means it's an install-mode
                # registration - the main bootstrapper already removes these sub-components.
                $_.UninstallString -notmatch '^MsiExec\.exe /I\{'
            }
    }
) | Sort-Object { [version]($_.DisplayVersion -replace '[^0-9.]') } -Descending

if ($ssmsEntries.Count -eq 0) {
    Write-DbaLog 'No SSMS installation detected. Nothing to uninstall.' 'Yellow'
    exit 0
}

$script:uninstallCompletedCleanly = $false
$script:anyAttempted = $false
$script:attemptedNames = [System.Collections.Generic.List[string]]::new()

foreach ($entry in $ssmsEntries) {
    $version = $entry.DisplayVersion
    $name    = $entry.DisplayName
    $major   = [int]($version -split '\.')[0]

    Write-DbaLog ''
    Write-DbaLog "Found : $name  v$version" 'White'

    if ($WhatIf) {
        $method = if ($major -ge 21) { 'VS Installer CLI or winget' } else { 'WiX /uninstall /quiet /norestart' }
        Write-DbaLog "  [WhatIf] Would uninstall '$name' via $method." 'Yellow'
        continue
    }

    if (-not $Force) {
        $answer = (Read-Host "  Uninstall '$name' v$version now? (yes/no)").Trim()
        if ($answer -notmatch '^(yes|y)$') {
            Write-DbaLog '  Skipped by user.' 'Yellow'
            continue
        }
    }

    # -- Check if SSMS is running ----------------------------------------------
    $ssmsProc  = Get-Process -Name 'ssms' -ErrorAction SilentlyContinue
    $forceClose = $false
    if ($ssmsProc) {
        $pidList = ($ssmsProc | ForEach-Object { $_.Id }) -join ', '
        Write-DbaLog "  WARNING: SSMS is currently running (PID $pidList)." 'Yellow'
        if ($Force) {
            Write-DbaLog "  -Force specified - will close SSMS automatically." 'Yellow'
            $forceClose = $true
        }
        else {
            $closeAnswer = (Read-Host "  Force close SSMS before uninstalling? (yes/no)").Trim()
            if ($closeAnswer -notmatch '^(yes|y)$') {
                Write-DbaLog "  Skipped - close SSMS manually and re-run." 'Yellow'
                continue
            }
            $forceClose = $true
        }
    }

    # -- SSMS 21+ (Visual Studio Installer) -----------------------------------
    if ($major -ge 21) {
        $script:anyAttempted = $true
        $script:attemptedNames.Add($name)
        Write-DbaLog "  Uninstalling '$name'..." 'Cyan'
        $uninstalled = $false
        $forceFlag   = if ($forceClose) { ' --force' } else { '' }
        $uiFlag      = if ($Passive)    { '--passive' } else { '--quiet' }

        $vswhere     = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe'
        $vsInstaller = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe'

        # Helper: run setup.exe, wait, stream elapsed, check result
        function Invoke-VsUninstall {
            param([string]$Exe, [string]$CmdArgs, [string]$EntryName)
            $fullCmd = "  Running: $Exe $CmdArgs"
            Write-Host "[$(Get-Date -Format 'HH:mm:ss')] $fullCmd" -ForegroundColor DarkGray
            Add-Content -Path $logFile -Value "[$(Get-Date -Format 'HH:mm:ss')] $fullCmd"
            $proc = Start-Process -FilePath $Exe -ArgumentList $CmdArgs -PassThru `
                -RedirectStandardOutput "$logFile.vs-stdout.txt" `
                -RedirectStandardError  "$logFile.vs-stderr.txt"
            $sw = [System.Diagnostics.Stopwatch]::StartNew()
            while (-not $proc.HasExited) {
                Write-Host ("`r    ... {0:mm\:ss} elapsed" -f $sw.Elapsed) -NoNewline -ForegroundColor DarkGray
                Start-Sleep -Seconds 2
            }
            Write-Host ''
            if (Test-Path "$logFile.vs-stderr.txt") {
                $vsErr = (Get-Content "$logFile.vs-stderr.txt" -Raw)
                if ($vsErr) { Write-DbaLog "  VS Installer: $($vsErr.Trim())" 'DarkGray' }
            }
            # Check only for this specific entry, not all SSMS installs
            $stillThere = @(foreach ($p in $regPaths) {
                Get-ItemProperty $p -ErrorAction SilentlyContinue |
                    Where-Object { $_.DisplayName -eq $EntryName -and $_.UninstallString -notmatch '^MsiExec\.exe /I\{' }
            })
            return ($stillThere.Count -eq 0)
        }

        # 1. vswhere - productId + channelId (avoids --installPath entirely)
        if ((Test-Path $vswhere) -and (Test-Path $vsInstaller)) {
            try {
                $vsJson      = & $vswhere -all -products '*' -format json
                $vsProducts  = $vsJson | ConvertFrom-Json
                # Match by installationPath extracted from registry UninstallString so we
                # pick the right entry when multiple SSMS versions are installed side-by-side.
                $regInstallPath = $null
                $rawCmdForMatch = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }
                if ($rawCmdForMatch -match '--installPath\s+"([^"]+)"') { $regInstallPath = $Matches[1] }
                $ssmsProduct = @($vsProducts) |
                    Where-Object {
                        $_.displayName -like '*SQL Server Management Studio*' -and
                        (-not $regInstallPath -or $_.installationPath -eq $regInstallPath)
                    } |
                    Select-Object -First 1

                if ($ssmsProduct) {
                    $channelPart = if ($ssmsProduct.channelId) { " --channelId $($ssmsProduct.channelId)" } else { '' }
                    $vsArgs = "uninstall --productId $($ssmsProduct.productId)$channelPart $uiFlag --norestart$forceFlag"
                    if (Invoke-VsUninstall -Exe $vsInstaller -CmdArgs $vsArgs -EntryName $name) {
                        $script:uninstallCompletedCleanly = $true
                        $uninstalled = $true
                    }
                } else {
                    Write-DbaLog '  vswhere: SSMS not found in VS Installer catalogue - trying registry fallback.' 'DarkGray'
                }
            }
            catch { Write-DbaLog "  vswhere error: $($_.Exception.Message)" 'DarkGray' }
        }

        # 2. Registry passthrough - use the stored VS Installer command but swap --installPath
        #    for --productId when the installer rejects it (seen on SSMS 21 Preview builds)
        if (-not $uninstalled) {
            $rawCmd = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }
            if ($rawCmd -match 'setup\.exe') {
                $regExe  = $null; $regArgs = $null
                if ($rawCmd -match '^"([^"]+)"(.*)$')  { $regExe = $Matches[1]; $regArgs = $Matches[2].Trim() }
                elseif ($rawCmd -match '^(\S+)(.*)$')   { $regExe = $Matches[1]; $regArgs = $Matches[2].Trim() }

                if ($regExe -and (Test-Path $regExe)) {
                    # If --installPath is present, try replacing with --productId derived from path
                    if ($regArgs -match '--installPath\s+"[^"]+"') {
                        $derivedId  = 'Microsoft.VisualStudio.Product.Ssms'
                        $regArgs    = $regArgs -replace '--installPath\s+"[^"]+"', "--productId $derivedId"
                        Write-DbaLog '  Registry fallback: replaced --installPath with --productId.' 'DarkGray'
                    }
                    if ($regArgs -notmatch '--quiet|--passive') { $regArgs += " $uiFlag" }
                    if ($regArgs -notmatch '--norestart')       { $regArgs += ' --norestart' }
                    if ($forceClose -and $regArgs -notmatch '--force') { $regArgs += ' --force' }
                    if (Invoke-VsUninstall -Exe $regExe -CmdArgs $regArgs.Trim() -EntryName $name) {
                        $script:uninstallCompletedCleanly = $true
                        $uninstalled = $true
                    }
                }
            }
        }

        # 3. Winget fallback (tries stable ID then preview ID then display name)
        if (-not $uninstalled) {
            $winget = Get-Command winget.exe -ErrorAction SilentlyContinue
            if ($winget) {
                $wingetAttempts = @(
                    @('uninstall', '--id', 'Microsoft.SQLServerManagementStudio',         '-e', '--silent', '--accept-source-agreements'),
                    @('uninstall', '--id', 'Microsoft.SQLServerManagementStudio.Preview',  '-e', '--silent', '--accept-source-agreements'),
                    @('uninstall', '--name', "`"$name`"",                                  '-e', '--silent', '--accept-source-agreements')
                )
                foreach ($wingetArgs in $wingetAttempts) {
                    $proc = Start-Process -FilePath $winget.Source -ArgumentList $wingetArgs -Wait -PassThru -NoNewWindow
                    if ($proc.ExitCode -in 0, 3010) {
                        $script:uninstallCompletedCleanly = $true
                        $uninstalled = $true
                        break
                    }
                }
            }
        }

        if ($uninstalled) {
            Write-DbaLog '  Uninstall completed.' 'Green'
        } else {
            Write-DbaLog '  Uninstall failed - remove manually via Settings -> Apps or:' 'Red'
            Write-DbaLog '    winget uninstall --id Microsoft.SQLServerManagementStudio.Preview -e' 'DarkGray'
            Write-DbaLog '    winget uninstall --id Microsoft.SQLServerManagementStudio -e' 'DarkGray'
        }
        continue
    }

    # -- SSMS 20 and below (WiX installer) ------------------------------------
    $rawCmd = if ($entry.QuietUninstallString) { $entry.QuietUninstallString } else { $entry.UninstallString }

    if (-not $rawCmd) {
        Write-DbaLog "  ERROR: No uninstall command in registry for '$name'. Use Settings -> Apps." 'Red'
        continue
    }

    if ($rawCmd -match '^"([^"]+)"(.*)$') {
        $exePath      = $Matches[1]
        $existingArgs = $Matches[2].Trim()
    }
    elseif ($rawCmd -match '^(\S+)(.*)$') {
        $exePath      = $Matches[1]
        $existingArgs = $Matches[2].Trim()
    }
    else {
        Write-DbaLog "  ERROR: Cannot parse uninstall command." 'Red'; continue
    }

    if (-not (Test-Path $exePath)) {
        Write-DbaLog "  ERROR: Uninstall EXE not found: $exePath" 'Red'; continue
    }

    $silentArgs = [System.Collections.Generic.List[string]]::new()
    if ($existingArgs -notmatch '/uninstall') { $silentArgs.Add('/uninstall') }
    if ($existingArgs -notmatch '/quiet')     { $silentArgs.Add('/quiet') }
    if ($existingArgs -notmatch '/norestart') { $silentArgs.Add('/norestart') }
    $finalArgs = ($existingArgs + ' ' + ($silentArgs -join ' ')).Trim()

    $script:anyAttempted = $true
    $script:attemptedNames.Add($name)
    Write-DbaLog "  Uninstalling '$name'..." 'Cyan'
    Write-DbaLog "  Running: $exePath $finalArgs" 'DarkGray'
    $wixOk = $false
    try {
        $proc = Start-Process -FilePath $exePath -ArgumentList $finalArgs -PassThru
        $sw   = [System.Diagnostics.Stopwatch]::StartNew()
        while (-not $proc.HasExited) {
            Write-Host ("`r    ... {0:mm\:ss} elapsed" -f $sw.Elapsed) -NoNewline -ForegroundColor DarkGray
            Start-Sleep -Seconds 2
        }
        Write-Host ''
        switch ($proc.ExitCode) {
            0    { Write-DbaLog '  Uninstall completed.' 'Green'; $wixOk = $true }
            3010 { Write-DbaLog '  Uninstall completed - reboot required.' 'Yellow'; $wixOk = $true }
            1626 { Write-DbaLog "  Exit 1626 - reboot this machine and retry, or use appwiz.cpl to remove manually." 'Yellow' }
            default { Write-DbaLog "  Uninstall exited with code $($proc.ExitCode) - trying winget fallback..." 'Yellow' }
        }
    }
    catch {
        Write-DbaLog "  ERROR during uninstall: $($_.Exception.Message)" 'Red'
    }

    if (-not $wixOk) {
        $winget = Get-Command winget.exe -ErrorAction SilentlyContinue
        if ($winget) {
            $wingetAttempts = @(
                @('uninstall', '--id',   'Microsoft.SQLServerManagementStudio',   '-e', '--silent', '--accept-source-agreements'),
                @('uninstall', '--name', "`"$name`"",                              '-e', '--silent', '--accept-source-agreements')
            )
            foreach ($wingetArgs in $wingetAttempts) {
                $wp = Start-Process -FilePath $winget.Source -ArgumentList $wingetArgs -Wait -PassThru -NoNewWindow
                if ($wp.ExitCode -in 0, 3010) {
                    Write-DbaLog '  Uninstall completed via winget.' 'Green'
                    $wixOk = $true
                    break
                }
            }
        }
        if (-not $wixOk) {
            # Last resort: find MSI product code directly from registry and call MsiExec /X
            # bypasses the Burn bootstrapper entirely, useful when Package Cache is corrupt
            $productCode = $null
            foreach ($p in $regPaths) {
                $match = Get-ItemProperty $p -ErrorAction SilentlyContinue |
                    Where-Object {
                        $_.DisplayName -eq $name -and
                        $_.PSChildName -match '^\{[0-9A-F-]+\}$'
                    } | Select-Object -First 1
                if ($match) { $productCode = $match.PSChildName; break }
            }
            if ($productCode) {
                Write-DbaLog "  Trying MsiExec direct removal (product code $productCode)..." 'DarkGray'
                $mp = Start-Process -FilePath 'MsiExec.exe' -ArgumentList "/X$productCode /qn /norestart" -Wait -PassThru
                if ($mp.ExitCode -in 0, 3010) {
                    Write-DbaLog '  Uninstall completed via MsiExec.' 'Green'
                    $wixOk = $true
                } else {
                    Write-DbaLog "  MsiExec exited with code $($mp.ExitCode)." 'Yellow'
                }
            }
        }
        if (-not $wixOk) {
            Write-DbaLog '  Uninstall failed - remove manually via Settings -> Apps or:' 'Red'
            Write-DbaLog "    winget uninstall --name `"$name`" -e" 'DarkGray'
        }
    }
}

# -- Confirm removal -----------------------------------------------------------
if (-not $WhatIf -and $script:anyAttempted) {
    Write-DbaLog ''
    Write-DbaLog 'Verifying removal...' 'Cyan'
    $stillInstalled = @(
        foreach ($p in $regPaths) {
            Get-ItemProperty $p -ErrorAction SilentlyContinue |
                Where-Object {
                    $script:attemptedNames -contains $_.DisplayName -and
                    $_.UninstallString -notmatch '^MsiExec\.exe /I\{'
                }
        }
    )
    if ($stillInstalled.Count -eq 0) {
        Write-DbaLog 'SSMS successfully removed.' 'Green'
    }
    else {
        foreach ($e in $stillInstalled) {
            Write-DbaLog "Still detected: $($e.DisplayName) v$($e.DisplayVersion)" 'Yellow'
            if ($script:uninstallCompletedCleanly) {
                Write-DbaLog "  Uninstaller exited cleanly - a reboot is likely required to finish removal." 'Yellow'
            }
            else {
                Write-DbaLog "  The uninstall may have been cancelled." 'Yellow'
                Write-DbaLog "  Re-run this script to try again, or use: Settings -> Apps -> Uninstall" 'DarkGray'
            }
        }
    }
}

Write-DbaLog ''
Write-DbaLog 'Done.' 'Green'
Write-DbaLog "Log: $logFile"

Example Output

Run -WhatIf first. It needs no elevation and removes nothing, though it still writes its log file.

uninstall-ssms.ps1 -WhatIf in an administrator PowerShell window: it finds SQL Server Management Studio 22 v22.11.0, prints WhatIf: would uninstall it via the VS Installer CLI or winget, then Done and the log file path. Nothing is removed.

On the lab after the update to 22.11.0: one install found, the route it would take, and nothing removed.

It names the route for each install it finds: Visual Studio Installer for SSMS 21 and later, WiX for 17 to 20. It does not run vswhere or check whether SSMS is open, so it cannot tell you which fallback a real run will need.


How To Run From The Repo

git clone https://github.com/peterwhyte-lgtm/dba-tools
cd dba-tools
.\Initialize-Environment.ps1

.\powershell\patching\ssms\uninstall-ssms.ps1 -WhatIf
.\powershell\patching\ssms\uninstall-ssms.ps1
.\powershell\patching\ssms\uninstall-ssms.ps1 -Force

This script lives in the repo at:


Understanding the Results

  • “Uninstall completed via winget” or “via MsiExec” means the WiX uninstaller failed on SSMS 17 to 20. If the same fallback fires on every machine, look at the environment, not the box. On SSMS 21 and later the line is just “Uninstall completed.” The last “Running:” line above it is the command that worked, unless winget took over, which logs no line of its own.
  • “Still detected” after a clean exit usually means a reboot is pending, not a failed removal. The script says so: “a reboot is likely required to finish removal”. If the uninstaller did not exit cleanly, it says the uninstall may have been cancelled.
  • MSI sub-components are skipped on purpose. Entries whose uninstall string starts with MsiExec.exe /I{ are Language Pack and base-MSI registrations that the main bootstrapper already handles. Counting them as separate installs would give false “still installed” results.
  • Exit codes. On SSMS 17 to 20, 0 and 3010 (success, reboot required) count as done. Any other code, 1603 included, moves on to winget and then MsiExec. On SSMS 21 and later the script does not read the installer’s exit code. It checks the registry instead. When that removal fails, the SSMS error code table on MS Docs names 1003 (SSMS is in use) and 8006 (SSMS processes running), and points at the installer logs in %TEMP% for anything else.

Best Practices

  • Run with -WhatIf first, especially on a machine with an older or unfamiliar SSMS, to see which installs it found and the route each one takes.
  • Close SSMS first. On SSMS 21 and later, -Force (or a yes at the prompt) closes it for you by passing the installer’s --force, which MS Docs warns can lose unsaved work. On SSMS 17 to 20 nothing closes it.
  • -Force also skips the per-install question, so it removes every SSMS version it finds, side-by-side installs included. Leave it off on a box where one version should stay.
  • Across a fleet, compare the logs. Which method worked on each machine tells you about install-state drift, not just pass or fail.
  • If every method fails, remove it through Settings → Apps as the script says, before you treat the machine as stuck.

Frequently Asked Questions

The uninstaller reports success but SSMS still shows in Settings → Apps, what happened?

Look for “Uninstaller exited cleanly - a reboot is likely required to finish removal.” at the end of the run. The installer can report success while the removal finishes on the next restart, so reboot before treating it as a failure. The pending-reboot note at the start of a run goes to the console only, not to the log.

Why does the script try more than one removal method?

Because SSMS uninstalls fail in different ways, depending on how the machine got there. A corrupted Package Cache, a vswhere catalogue mismatch or a stale registry entry each need a different workaround. The first method covers the common case. The fallbacks cover the rest.

Do I have to uninstall SSMS 20 before installing SSMS 22?

No. Per MS Docs, SSMS 22 installs side by side with an earlier major version. Install and Update SSMS via PowerShell stops when it finds an older SSMS, because it chooses not to add a second copy. So either run this script first, or install SSMS 22 by hand alongside the old one.

Can I uninstall SSMS with winget?

Sometimes, and by name more reliably than by id. In the winget catalogue, Microsoft.SQLServerManagementStudio is SSMS 20. SSMS 21 and 22 have their own .21 and .22 ids, and 18 and 19 have none.

An SSMS installed from the downloaded installer may match no id at all. With SSMS 22 installed that way, winget list --id Microsoft.SQLServerManagementStudio.22 -e found nothing, and winget list --name "SQL Server Management Studio 22" -e found it. winget uninstall takes the same --name and -e options.

For SSMS 21 and later, this script’s winget step tries 2 ids first, neither of them SSMS 21 or 22. Then it tries the exact display name, and that is the attempt that can match.

SSMS 21 or 22 will not uninstall by any method, what now?

MS Docs’ last resort is InstallCleanup.exe in C:\Program Files (x86)\Microsoft Visual Studio\Installer. Run it from an administrator prompt with the Visual Studio Installer closed. -i 22 removes only version 22 products. MS Docs warns it can remove features other Visual Studio installs use, so use it only after repair and uninstall have both failed. This script does not run it.


Summary

The counterpart to Install and Update SSMS via PowerShell. A clean uninstall is not one command. It is finding the removal method that works on that machine, so the script starts with the installer itself, moves to the next method when one fails, and checks the registry before it calls the job done.

Run it when the install script stops at an older SSMS, or whenever a corrupted install will not clear through Settings → Apps. Start with -WhatIf, close SSMS, and leave -Force off where one version should stay.

Where To Go Next

This page covers one part of working with SSMS. The full reference ties the install, configuration and troubleshooting pieces together.

More in this series: Install and Update SQL Server Management Studio (SSMS) · DBA Scripts: Install and Update SSMS via PowerShell · Open SSMS as a Different Domain User · How to Show Line Numbers in SSMS · How to Increase Maximum Characters Displayed in SSMS

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *