Install SQL Server Management Studio on a Host Without Internet Access

Install SQL Server Management Studio on a Host Without Internet Access

Install SQL Server Management Studio on a Host Without Internet Access

This guide shows how to install, upgrade, and uninstall SQL Server Management Studio (SSMS) on a host with no internet access. It is for SSMS 21 and the versions after it.

SSMS 21 and the versions after it use a Visual Studio-style bootstrapper, vs_SSMS.exe. SSMS 20 and the versions before it use an MSI installer. The bootstrapper downloads its packages when you start it. An offline host cannot download these packages.

The solution has two parts. In Stage 1, you make an offline layout on a machine with internet access. You make a zip file of the layout and put it in a repository. In Stage 2, the offline host downloads the zip file and installs SSMS from it.

This guide uses the repository name ZipVault as an example. Use a repository that you have, for example JFrog Artifactory, Sonatype Nexus, or a file share. Change each example URL to the URL of your repository.

Do each step on a non-production host before you use a production host.

Contents


Requirements

The machine that makes the layout must have:

  • Windows 11 or Windows Server 2022
  • Internet access
  • PowerShell 7 or a version after it
  • Access to your repository
  • Free disk space for the layout and for the zip file

The offline host must have:

  • Windows 11, Windows Server 2025, Windows Server 2022, or Windows Server 2019, 64-bit. See the Microsoft system requirements for SSMS.
  • Administrator rights
  • Access to your repository
  • The Microsoft Visual C++ v14 Redistributable, x86 and x64. An offline install must have it, as the Microsoft system requirements page shows.
  • .NET Framework 4.8. SSMS 22 must have it. Setup installs it when it is missing, as the Microsoft system requirements page shows.

Stage 1: Make the Offline Layout and Put It in a Repository

Do Stage 1 one time on a machine with internet access. Do it again each time Microsoft releases a new SSMS version.

Do all steps of Stage 1 in PowerShell 7 or a version after it. To see your version, use the first command in the next code block. If Major is less than 7, start pwsh.

$PSVersionTable.PSVersion # If Major is less than 7, start pwsh. pwsh

Step 1: Make the Staging Folder

$ProgressPreference = 'SilentlyContinue' $env:SSMS_STAGE = 'C:\stage' New-Item -ItemType Directory -Force -Path $env:SSMS_STAGE

Step 2: Find the Version and the Download URL

Open the Microsoft SSMS release history page:

https://learn.microsoft.com/en-us/ssms/release-history

Find the version to deploy on that page. For SSMS 22, use the Evergreen bootstrapper table. For SSMS 21 and the versions before it, use the Fixed version installers table. Record the version number and the direct download URL. Then set them:

$version = '22.10.2' $bootstrapperUrl = 'https://aka.ms/ssms/22/release/vs_SSMS.exe' $env:SSMS_LAYOUT = "$env:SSMS_STAGE\ssmsvnext-layout-$version"

Note: The URL changes with each release. The aka.ms link in this example always gives the newest release.

Use only the URL from the release history page. For a previous version, use the direct link for that version.

Step 3: Download the Bootstrapper

Invoke-WebRequest -Uri $bootstrapperUrl -OutFile "$env:SSMS_STAGE\vs_SSMS.exe" *> $null

This step downloads only the bootstrapper. The file size is approximately 5 MB. The next step makes the full layout.

Step 4: Make the Offline Layout

& "$env:SSMS_STAGE\vs_SSMS.exe" --layout $env:SSMS_LAYOUT --lang en-US --quiet --wait

This step downloads the full offline layout into $env:SSMS_LAYOUT. The layout size is approximately 2.5 GB. The download time can be long on a slow connection.

The full layout path must be less than 80 characters long. The Create an offline installation of SSMS page gives this information.

Note: The bootstrapper opens a new window.

Wait for that window to close. Do not continue to Step 4a until the window is closed.

Step 4a: Add the Microsoft Code Signing Certificate

A host behind a firewall cannot validate the certificate chain of the Visual Studio Installer at install time. Stage 2 installs the certificate on the offline host.

Download the Microsoft Windows Code Signing PCA 2024 certificate. Add it to the certificates folder of the layout.

Invoke-WebRequest -Uri "https://www.microsoft.com/pkiops/certs/Microsoft%20Windows%20Code%20Signing%20PCA%202024.crt" ` -OutFile "$env:SSMS_LAYOUT\certificates\Microsoft Windows Code Signing PCA 2024.crt" *> $null

Note: This certificate has an expiration date.

If an install stops with a certificate error after the expiration date, find a new version in the Microsoft PKI repository. Then do this step again with the new file.

Step 5: Make a Zip File of the Layout Folder

Compress-Archive -Path "$env:SSMS_LAYOUT\*" -DestinationPath "$env:SSMS_STAGE\ssms-vnext-$version.zip" -Force *> $null

Example output: C:\stage\ssms-vnext-22.10.2.zip

Note: In a test, Compress-Archive with \* did not add hidden files.

Step 6: Get the SHA256 Checksum

Get-FileHash "$env:SSMS_STAGE\ssms-vnext-$version.zip" -Algorithm SHA256

Record the checksum. Stage 2 uses it.

Example output:

Algorithm Hash Path --------- ---- ---- SHA256 A1B2C3D4E5F6... C:\stage\ssms-vnext-22.10.2.zip

Step 7: Upload the Zip File to the Repository

Upload the zip file to your repository. This example uses the repository name ZipVault. Change the URL to the URL of your repository.

$repoUrl = "https://zipvault.example.com/repository/sql-tools/ssms/ssms-vnext-$version.zip" Invoke-WebRequest -Uri $repoUrl -Method Put -InFile "$env:SSMS_STAGE\ssms-vnext-$version.zip"

If your repository has a login, add the -Credential parameter or an authorization header to the command. If you use a file share, use Copy-Item to put the zip file there.

Step 8: Remove the Staging Folder

This step removes the staging folder from the machine that made the layout.

Remove-Item $env:SSMS_STAGE -Recurse -Force

Step 9: Record the Version and the Checksum

Record three values: the version, the SHA256 checksum from Step 6, and the repository URL from Step 7. Each person who installs SSMS must have all three values.


Stage 2: Install or Upgrade SSMS on the Offline Host

Do Stage 2 on each offline host. Do each step in the same PowerShell window. Open the window as Administrator. Each step uses the values that the previous steps set.

If you close the window, the values are lost. Open a new window as Administrator. Do Step 1 and Step 6 again. Then continue with the step where you stopped.

Stage 2 installs a new version or upgrades an installed version. Step 6 selects the correct command. If this version of SSMS is installed, do only Steps 1 to 3, Step 10, and Step 12.

Step 1: Set the Values

Use the version, the checksum, and the repository URL that you recorded in Stage 1.

$ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $version = '22.10.2' $majorVersion = ([version]$version).Major $checksum = 'PASTE_THE_SHA256_HASH_FROM_STAGE_1_STEP_6' $repoUrl = "https://zipvault.example.com/repository/sql-tools/ssms/ssms-vnext-$version.zip" $zipPath = Join-Path $env:TEMP "ssms-vnext-$version.zip" $extractPath = Join-Path $env:TEMP "ssms-vnext-$version"

Step 2: Download the Zip File and Compare the Checksum

Download the zip file from the repository. The file size is approximately 2.5 GB. Then compare its SHA256 checksum with the value that you recorded.

if (Test-Path $zipPath) { Remove-Item $zipPath -Force } Invoke-WebRequest -Uri $repoUrl -OutFile $zipPath $actual = (Get-FileHash $zipPath -Algorithm SHA256).Hash if ($actual -ne $checksum) { throw "Checksum mismatch. Expected $checksum but found $actual." }

If the checksums are different, stop. Download the file again.

Step 3: Extract the Zip File

Extract the zip file to a temporary folder. This step does not use the internet.

if (Test-Path $extractPath) { Remove-Item $extractPath -Recurse -Force } Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractPath)

Step 4: Install the Certificates

Install the certificates from the layout into the trust store of the host. A host behind a firewall must do this step, because it cannot get access to the Microsoft certificate servers.

Note: The checksum shows that the zip file did not change after Stage 1. It does not show who made the certificates.

Compare the SHA-1 thumbprint of each .cer file with the thumbprint on the Microsoft PKI repository page. The page lists Microsoft Root Certificate Authority 2010 and 2011. It does not list the .crt file.

$certPath = Join-Path $extractPath 'certificates' if (Test-Path $certPath) { Get-ChildItem $certPath -Filter '*.cer' | ForEach-Object { certutil -addstore -f "Root" $_.FullName | Out-Null } Get-ChildItem $certPath -Filter '*.crt' | ForEach-Object { certutil -addstore -f "CA" $_.FullName | Out-Null } }

Step 5: Close SSMS

Caution: Tell the users before you do this step. This command stops SSMS for all users on the host. SSMS does not keep work that you did not save.

Use this command to close SSMS on the host. If an SSMS session is open, the install stops with error 1003 or 8006.

Get-Process -Name 'Ssms' -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep -Seconds 5

Step 6: Select the Install Command

If the same major version is installed, for example 22.4 to 22.5, use update, an in-place upgrade. If no version is installed, or a different major version is installed, use the default install command. For example, use it for a new install, or for a change from 21.x to 22.x. A new install also adds the BI and Code Tools workloads. The update command cannot use --add.

$regPaths = @( 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) $sameMajorInstalled = foreach ($p in $regPaths) { Get-ItemProperty $p -ErrorAction SilentlyContinue | Where-Object { ($_.DisplayName -like '*SQL Server Management Studio*' -or $_.DisplayName -like '*SSMS*') -and $_.DisplayVersion -like "$majorVersion.*" } } $bootstrapVerb = if ($sameMajorInstalled) { 'update' } else { $null } $bootstrapAddArgs = if (-not $bootstrapVerb) { '--add Microsoft.SqlServer.Workload.SSMS.BI --add Microsoft.SqlServer.Workload.SSMS.CodeTools' } else { $null } $bootstrapArgs = (@($bootstrapVerb, $bootstrapAddArgs, '--noWeb', '--quiet', '--norestart') | Where-Object { $_ }) -join ' ' $bootstrapArgs # These are the arguments for Step 7.

Step 7: Start the Bootstrapper

Start the bootstrapper from the extracted layout. Do not add --wait.

The bootstrapper gives the work to setup.exe, the child process of the Visual Studio Installer. The bootstrapper can exit before setup.exe completes the install.

$bootstrapper = Join-Path $extractPath 'vs_SSMS.exe' if (-not (Test-Path $bootstrapper)) { throw "vs_SSMS.exe not found in $extractPath" } $proc = Start-Process -FilePath $bootstrapper -ArgumentList $bootstrapArgs -PassThru

Step 8: Wait for the Visual Studio Installer to Complete

Wait up to 2 minutes for the bootstrapper to stop. Then wait 30 seconds for setup.exe to start. Then wait for setup.exe to complete.

The code stops after 30 minutes. An SSMS install is 5 to 10 minutes long on most hosts.

$proc | Wait-Process -Timeout 120 -ErrorAction SilentlyContinue Start-Sleep -Seconds 30 $timeoutSeconds = 1800 $elapsed = 0 $interval = 15 while ($elapsed -lt $timeoutSeconds) { $vsSetup = Get-Process -Name 'setup' -ErrorAction SilentlyContinue | Where-Object { $_.Path -like '*Microsoft Visual Studio\Installer*' } if (-not $vsSetup) { break } Start-Sleep -Seconds $interval $elapsed += $interval } if ($elapsed -ge $timeoutSeconds) { throw "The install timed out after 30 minutes. Read the logs in $env:TEMP." } "Installer exit code: $($proc.ExitCode)"

Note: The exit code in Step 8 is the exit code of the bootstrapper. Step 9 gives the result of the install.

Step 9: Make Sure That the Version Is Correct

Make sure that SSMS is in the registry or on disk. Make sure that the version is the target version or a version after it.

$installed = foreach ($p in $regPaths) { Get-ItemProperty $p -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '*SQL Server Management Studio*' -or $_.DisplayName -like '*SSMS*' } } $ssmsExe = Get-Item 'C:\Program Files\Microsoft SQL Server Management Studio*\Release\Common7\IDE\Ssms.exe' ` -ErrorAction SilentlyContinue if (-not $installed -and -not $ssmsExe) { throw "SSMS is not in the registry and not on disk. Read the logs in $env:TEMP." } $detectedVersion = $null if ($installed) { $entry = $installed | Select-Object -First 1 if ($entry.DisplayVersion) { $detectedVersion = [version]$entry.DisplayVersion } } if (-not $detectedVersion -and $ssmsExe) { $firstExe = $ssmsExe | Select-Object -First 1 $detectedVersion = [version]$firstExe.VersionInfo.ProductVersion } if ($detectedVersion -and $detectedVersion -lt [version]$version) { throw "SSMS $detectedVersion is installed, but the target version $version was not reached. The install or the update can have been blocked, or can have failed." } "SSMS $detectedVersion is installed."

The installed build number is frequently more accurate than $version. For example, $version can be 22.8.0, and the installed build can be 22.8.12009.203. The code does not stop in this example.

Note: Step 9 uses the first SSMS entry in the registry. If the host has two major versions of SSMS, that entry can be the previous version. Then Step 9 can stop although the install is correct. To see all versions, use the entry list in Step 11.

Step 10: Add the BI and Code Tools Workloads

A new install adds these two workloads in Step 6. The update command cannot add them. A host can have this version without these two workloads. This step finds the workloads that the host does not have and adds them with a modify command.

Use the vs_SSMS.exe file from the layout for the modify command. Do not use the setup.exe file on the host. The setup.exe file has no source for a workload that the host did not install before.

$bootstrapper = Join-Path $extractPath 'vs_SSMS.exe' $vswhereExe = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' $requiredWorkloads = @('Microsoft.SqlServer.Workload.SSMS.BI', 'Microsoft.SqlServer.Workload.SSMS.CodeTools') $missingWorkloads = @() if (Test-Path $vswhereExe) { foreach ($workload in $requiredWorkloads) { $present = & $vswhereExe -all -products '*' -requires $workload -property installationPath 2>$null if (-not $present) { $missingWorkloads += $workload } } } else { "vswhere.exe not found. Cannot confirm the BI and Code Tools workloads." } if ($missingWorkloads.Count -gt 0) { $channelId = "SSMS.$majorVersion.SSMS.Release" $addArgs = ($missingWorkloads | ForEach-Object { "--add $_" }) -join ' ' $modifyProc = Start-Process -FilePath $bootstrapper ` -ArgumentList "modify --noWeb --productID Microsoft.VisualStudio.Product.SSMS --channelID $channelId $addArgs --quiet --norestart" ` -Wait -PassThru "Added: $($missingWorkloads -join ', '). Exit code: $($modifyProc.ExitCode)" }

Exit code 0 or 3010 shows that the workloads are added.

Note: Step 10 uses the Release channel, SSMS.<major version>.SSMS.Release. This guide is for the Release channel.

Step 11: Uninstall a Previous Major Version (Optional)

Do this step only if a previous major version is installed. For example, SSMS 21 is on the host, and you install SSMS 22. Do not uninstall the version that you installed in Step 7.

For SSMS 21 and the versions after it, use the Visual Studio Installer. Change $oldMajor to the previous major version:

$oldMajor = 21 $setupExe = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe' Start-Process -FilePath $setupExe ` -ArgumentList "uninstall --installPath `"C:\Program Files\Microsoft SQL Server Management Studio $oldMajor\Release`" --quiet --norestart --noweb" ` -Wait

For SSMS 20 and the versions before it, use msiexec. First, show the entries. Find the GUID of the previous version:

foreach ($p in $regPaths) { Get-ItemProperty $p -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '*SQL Server Management Studio*' } | Select-Object PSChildName, DisplayName, DisplayVersion }

Then uninstall the previous version. Replace {GUID} with the PSChildName value:

Start-Process -FilePath 'msiexec' -ArgumentList '/x {GUID} /qn /norestart' -Wait

Step 12: Remove the Temporary Files

Remove-Item $zipPath -Force -ErrorAction SilentlyContinue Remove-Item $extractPath -Recurse -Force -ErrorAction SilentlyContinue

Note: The installer records the path of the layout. After Step 12, a later Repair or Modify from the Visual Studio Installer can ask for the layout.

To change the host again, do Stage 2 again with the zip file.

Log Files and Exit Codes

Exit code 3010 shows that the install completed and a reboot is necessary. Reboot the host when you can.

The install writes log files to %TEMP%\dd_vs_SSMS_decompression_log.txt and %TEMP%\dd_vs_SSMS*. In the Microsoft documentation, other log files start with ssms_bootstrapper, ssms_client, and ssms_setup.

If the install stops with no error message and SSMS is not installed, open the newest dd_bootstrapper_*.log file in %TEMP%. The message Certificate is invalid shows a certificate problem. See Install certificates for an offline installation of SSMS.

For the full list of exit codes, see the Microsoft command-line parameters.


Stage 3: Uninstall Fully

Use Stage 3 only when you must uninstall SSMS fully from a host. Stage 3 removes all versions and all components.

Close SSMS first. Save the next script as Uninstall-SSMS.ps1. Start it in a PowerShell window that you opened as Administrator.

$ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $setupExe = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe' $regHives = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' ) Write-Host 'Scanning for SSMS installations...' $ssmsEntries = foreach ($hive in $regHives) { if (Test-Path $hive) { Get-ChildItem $hive -ErrorAction SilentlyContinue | Get-ItemProperty -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '*SQL Server Management Studio*' -and $_.DisplayName -notlike '*Visual Studio Installer*' } } } # Use the correct uninstall method for each entry, by major version. $vsInstallerMajors = [System.Collections.Generic.HashSet[int]]::new() $msiEntries = [System.Collections.Generic.List[object]]::new() foreach ($entry in $ssmsEntries) { $major = 0 if ($entry.DisplayVersion -match '^(\d+)') { $major = [int]$Matches[1] } Write-Host "Found: '$($entry.DisplayName)' ($($entry.DisplayVersion))" if ($major -ge 21) { [void]$vsInstallerMajors.Add($major) } else { $msiEntries.Add($entry) } } if ($vsInstallerMajors.Count -eq 0 -and $msiEntries.Count -eq 0) { Write-Host 'No SSMS installations found. Nothing to uninstall.' exit 0 } # SSMS 21 and the versions after it: uninstall with the Visual Studio Installer, one call for each major version. foreach ($major in $vsInstallerMajors) { $installPath = "C:\Program Files\Microsoft SQL Server Management Studio $major\Release" Write-Host "Uninstalling SSMS $major (Visual Studio Installer): $installPath..." if (-not (Test-Path $setupExe)) { Write-Host " Visual Studio Installer not found at '$setupExe'. Cannot uninstall SSMS $major." continue } if (-not (Test-Path $installPath)) { Write-Host " Install path not found: $installPath. It can already be removed." continue } try { $proc = Start-Process -FilePath $setupExe ` -ArgumentList "uninstall --installPath `"$installPath`" --quiet --norestart --noweb" ` -Wait -PassThru if ($proc.ExitCode -eq 0 -or $proc.ExitCode -eq 3010) { Write-Host " Uninstalled SSMS $major (exit code $($proc.ExitCode))" } else { Write-Host " setup.exe exited $($proc.ExitCode). Read the logs in $env:TEMP." } } catch { Write-Host " Failed to uninstall SSMS $major`: $($_.Exception.Message)" } } # SSMS 20 and the versions before it: uninstall with msiexec. foreach ($entry in $msiEntries) { $guid = $entry.PSChildName Write-Host "Uninstalling '$($entry.DisplayName)' ($($entry.DisplayVersion)) with msiexec..." try { $proc = Start-Process -FilePath 'msiexec' ` -ArgumentList "/x $guid /qn /norestart" ` -Wait -PassThru # 0 = success, 3010 = success and a reboot is necessary, 1605 = not installed (the program is not on the host). if ($proc.ExitCode -eq 0 -or $proc.ExitCode -eq 3010 -or $proc.ExitCode -eq 1605) { Write-Host " Uninstalled: $($entry.DisplayName) (exit code $($proc.ExitCode))" } else { Write-Host " msiexec exited $($proc.ExitCode) for '$($entry.DisplayName)'. Read the event log." } } catch { Write-Host " Failed to uninstall '$($entry.DisplayName)'`: $($_.Exception.Message)" } } # Remove empty SSMS folders from C:\Program Files. Get-ChildItem 'C:\Program Files\Microsoft SQL Server Management Studio*' -Directory -ErrorAction SilentlyContinue | Where-Object { (Get-ChildItem $_.FullName -Force -ErrorAction SilentlyContinue).Count -eq 0 } | ForEach-Object { Write-Host "Removing empty folder: $($_.FullName)" Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue } Write-Host 'SSMS uninstall complete.'

The script does these steps:

  1. Examines the registry for SSMS entries.
  2. For an entry of SSMS 21 or a version after it, starts setup.exe uninstall --installPath for that major version.
  3. For an entry of SSMS 20 or a version before it, starts msiexec /x {GUID} /qn /norestart.
  4. Continues with the next entry after an error in one removal.
  5. Removes empty SSMS folders from C:\Program Files.

For the method with a user interface, see the Microsoft uninstall page. If a damaged install prevents the removal, the same page gives the InstallCleanup.exe tool. Use this tool only as the last step.

Comments

Popular posts from this blog

Windows Server Failover Clustering/SQL Server Firewall Access Rules

Windows Server Recommendations For AWS

Windows Server Failover Clustering Hotfixes for SQL Server AlwaysOn