Install the latest OpenSSH Server on Windows using a batch file

Leave it to Microsoft to screw up installing a single package....

I'd had a pair of scripts that would install SSH server (https://www.nodinrogers.com/post/2024-02-10-installing-ssh-in-windows/), but that took about 20 minutes.

Compare that to ~10 seconds on a Linux install...

Grabbing/installing the latest OpenSSH server version from Github bypasses this lengthy install, and takes under a minute.

Bonus, there is only one .bat file script!

The batch file below does all of that. It checks for administrator rights, detects the Windows architecture, downloads the matching MSI from the latest GitHub release, verifies its SHA-256 digest and Authenticode signature, installs it silently, and configures the sshd service.

Note: At the time of writing, the release marked Latest in the PowerShell/Win32-OpenSSH GitHub repository is also marked as a preview release and non-production ready. This script deliberately follows GitHub's releases/latest endpoint, so review the selected release before using it on a production system.

What the script does

The script completes the following steps:

  1. Confirms that it is running with administrator rights.
  2. Detects whether Windows is x64, ARM64, or 32-bit.
  3. Queries the GitHub API for the latest PowerShell/Win32-OpenSSH release.
  4. Downloads the MSI matching the computer's architecture.
  5. Compares the downloaded file with GitHub's SHA-256 digest.
  6. Requires a valid Windows Authenticode signature.
  7. Installs OpenSSH silently using msiexec.exe.
  8. Configures the sshd service to start automatically.
  9. Starts the SSH server.
  10. Creates a Windows Firewall rule allowing inbound TCP traffic on port 22.
  11. Deletes the downloaded installer and displays the service status.

Microsoft's OpenSSH documentation also configures sshd for automatic startup and permits inbound connections on TCP port 22. The difference here is that the script installs the MSI published in the Win32-OpenSSH GitHub repository instead of installing the Windows optional capability.

OpenSSH installation batch file

Save the following as install-openssh-server.bat:

@echo off

setlocal EnableExtensions

fltmc >nul 2>&1
if errorlevel 1 (
    echo ERROR: Right-click this file and select "Run as administrator".
    pause
    exit /b 1
)

if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
    set "MSI_PATTERN=OpenSSH-Win64*.msi"
) else if /i "%PROCESSOR_ARCHITEW6432%"=="AMD64" (
    set "MSI_PATTERN=OpenSSH-Win64*.msi"
) else if /i "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
    set "MSI_PATTERN=OpenSSH-ARM64*.msi"
) else (
    set "MSI_PATTERN=OpenSSH-Win32*.msi"
)

set "INSTALLER=%TEMP%\OpenSSH-Installer.msi"

echo Finding and downloading the latest OpenSSH MSI from GitHub...

powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$ErrorActionPreference='Stop'; $headers=@{'User-Agent'='OpenSSH-Windows-Installer';'Accept'='application/vnd.github+json';'X-GitHub-Api-Version'='2022-11-28'}; $release=Invoke-RestMethod -Headers $headers -Uri 'https://api.github.com/repos/PowerShell/Win32-OpenSSH/releases/latest'; $asset=@($release.assets) | Where-Object { $_.name -like '%MSI_PATTERN%' } | Select-Object -First 1; if (-not $asset) { throw 'A matching OpenSSH MSI was not found in the latest GitHub release.' }; if ($asset.digest -notmatch '^sha256:[0-9a-fA-F]{64}$') { throw 'GitHub did not provide a valid SHA-256 digest for this asset.' }; Write-Host ('Downloading ' + $asset.name); Invoke-WebRequest -UseBasicParsing -Uri $asset.browser_download_url -OutFile '%INSTALLER%'; $expected=$asset.digest.Substring(7); $actual=(Get-FileHash -LiteralPath '%INSTALLER%' -Algorithm SHA256).Hash; if ($actual -ine $expected) { throw ('SHA-256 verification failed. Expected ' + $expected + ', received ' + $actual + '.'); }; Write-Host ('SHA-256 verified: ' + $actual); $signature=Get-AuthenticodeSignature -LiteralPath '%INSTALLER%'; if ($signature.Status -ne 'Valid') { throw ('Authenticode verification failed: ' + $signature.Status + '. ' + $signature.StatusMessage); }; if (-not $signature.SignerCertificate) { throw 'The installer does not contain a signer certificate.' }; Write-Host ('Valid Authenticode signer: ' + $signature.SignerCertificate.Subject)" <nul

if errorlevel 1 (
    echo ERROR: The OpenSSH installer could not be downloaded or verified.
    del "%INSTALLER%" >nul 2>&1
    pause
    exit /b 1
)

if not exist "%INSTALLER%" (
    echo ERROR: The installer file was not created.
    pause
    exit /b 1
)

echo Installing OpenSSH...

msiexec.exe /i "%INSTALLER%" /quiet /norestart
set "MSI_RESULT=%ERRORLEVEL%"

if not "%MSI_RESULT%"=="0" if not "%MSI_RESULT%"=="3010" (
    echo ERROR: Installation failed with MSI exit code %MSI_RESULT%.
    del "%INSTALLER%" >nul 2>&1
    pause
    exit /b %MSI_RESULT%
)

echo Configuring the SSH server...

sc.exe config sshd start= auto >nul
if errorlevel 1 (
    echo ERROR: The sshd service could not be configured.
    del "%INSTALLER%" >nul 2>&1
    pause
    exit /b 1
)

sc.exe start sshd >nul 2>&1

netsh advfirewall firewall delete rule name="OpenSSH SSH Server" >nul 2>&1
netsh advfirewall firewall add rule name="OpenSSH SSH Server" dir=in action=allow protocol=TCP localport=22 >nul

if errorlevel 1 (
    echo ERROR: The Windows Firewall rule could not be created.
    del "%INSTALLER%" >nul 2>&1
    pause
    exit /b 1
)

del "%INSTALLER%" >nul 2>&1

echo.
echo OpenSSH was installed successfully.
echo SSH connections are permitted on TCP port 22.
echo.
sc.exe query sshd

echo Press any key to continue...

pause
endlocal

Run the installer

Right-click install-openssh-server.bat, then select Run as administrator.

The script uses fltmc as a simple elevation check. If the command cannot run with the required privileges, the script stops before downloading or installing anything.

The architecture checks deserve a little explanation. %PROCESSOR_ARCHITECTURE% normally identifies the architecture of the current process. %PROCESSOR_ARCHITEW6432% also catches a 32-bit command process running on 64-bit Windows. The resulting MSI pattern will be one of:

  • OpenSSH-Win64*.msi
  • OpenSSH-ARM64*.msi
  • OpenSSH-Win32*.msi

PowerShell then calls the GitHub Releases API and selects the first asset matching that pattern.

Installation results

The MSI is installed quietly and does not force a restart:

msiexec.exe /i "%INSTALLER%" /quiet /norestart

The script accepts MSI result 0, meaning success, and 3010, meaning the installation succeeded but Windows must be restarted before all changes take effect. Any other result is treated as an error.

After installation, the following command configures the OpenSSH server to start with Windows:

sc.exe config sshd start= auto

The space after start= is required by sc.exe.

The script then starts sshd and replaces its own firewall rule with one allowing inbound TCP connections on port 22:

netsh advfirewall firewall delete rule name="OpenSSH SSH Server"
netsh advfirewall firewall add rule name="OpenSSH SSH Server" dir=in action=allow protocol=TCP localport=22

This rule allows port 22 from any source permitted by the active Windows Firewall profiles. If the server should only accept SSH from a management network, restrict the rule's remote addresses instead of exposing the port more broadly.

Test the SSH server

At the end of the installation, the script runs:

sc.exe query sshd

Look for STATE showing RUNNING.

From another computer with an SSH client, connect using the Windows account name and the hostname or IP address of the server:

ssh username@windows-hostname

On a domain-joined computer, Microsoft documents the username format as:

ssh domain\username@windows-hostname

The first connection should display the server's host-key fingerprint. Verify that fingerprint before accepting it, especially when connecting across an untrusted network.

A few security considerations

This is a convenience script, not a complete OpenSSH hardening guide.

  • It downloads whichever release GitHub currently marks as latest. That may be a preview release.
  • It requires the downloaded MSI to match the SHA-256 digest published in the GitHub release-asset metadata and to have a valid Authenticode signature before installation.
  • It opens TCP port 22 without restricting the source IP address.
  • It does not configure SSH keys, allowed users, authentication methods, or other options in sshd_config.
  • It deletes and recreates any firewall rule named OpenSSH SSH Server, so do not reuse that exact name for a separately customized rule.

The SHA-256 digest and MSI come from the same GitHub release, so this detects a damaged or altered download but is not an independent defense against a compromised publisher account. Authenticode adds a separate certificate-chain and file-integrity check. For a production server, also pin a reviewed release, confirm the displayed signer, limit the firewall rule to trusted networks, and configure key-based authentication. The Windows OpenSSH configuration file is normally located at %ProgramData%\ssh\sshd_config.

References