Using PowerShell over SSH, Twenty Years Later
Twenty years ago this month I published instructions for wiring PowerShell to SSH with Cygwin. It involved a mirror hunt, an environment variable named ntsec, and a shell that gave you no prompt and no output when it started. The post ended with a plea to Microsoft: just use SSH. “Please don’t invent a proprietary Microsoft only tool to do this. Please please please please!”
They invented the proprietary tool anyway (WinRM), and for a while my Cygwin kludge was the #1 Google result for “PowerShell SSH”, which says more about the state of the ecosystem in 2008 than about the post. But the story has a good ending: Microsoft joined OpenSSH development, shipped it in Windows 10 in 2018, and as of Windows Server 2025 the SSH server is preinstalled on every box. The begging worked. It only took twelve years.
Here is the current state of PowerShell and SSH, as of mid-2026. This is the guide I wish had existed at any point in the last two decades.
What Ships Where
The OpenSSH client (ssh, scp, sftp, ssh-keygen, ssh-agent) has been in the box since Windows 10 build 1809 and Windows Server 2019. If you are on anything modern, typing ssh in a terminal just works. No PuTTY required (pour one out for a faithful friend).
The OpenSSH server is where the 2025 change landed: on Windows Server 2025, sshd is preinstalled. The service is present but stopped until you start it. On Windows 10, 11, Server 2019, and 2022 it remains an optional capability:
# Only needed before Server 2025
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# Everywhere: start it and keep it started
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
The firewall rule for port 22 is created automatically when the capability is installed. The in-box build trails upstream OpenSSH; if you want current releases, the Win32-OpenSSH project ships standalone MSIs, including a 10.0 preview that picks up upstream’s post-quantum key exchange (mlkem768x25519-sha256). Yes: the operating system I once had to trick into running a remote shell now ships quantum-resistant key agreement. It is allowed to feel a little vertiginous.
Make SSH Drop You Into PowerShell
Out of the box, sshing into a Windows machine lands you in cmd.exe, which is a time machine with none of the charm. The default shell is a registry value:
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" `
-Name DefaultShell `
-Value "C:\Program Files\PowerShell\7\pwsh.exe" `
-PropertyType String -Force
Point it at PowerShell 7 (pwsh.exe), not Windows PowerShell 5.1. PowerShell 7.6 is the current LTS release, it is a separate install from the OS, and everything below assumes it.
Keys, and the Gotcha That Bites Everyone
ssh-keygen on Windows defaults to Ed25519 now. Generate a key, then put the agent to work. The agent service exists on every modern Windows machine and is disabled by default, which is why nobody knows it exists:
# Run as administrator, once
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent
# As yourself
ssh-keygen -t ed25519
ssh-add $env:USERPROFILE\.ssh\id_ed25519
Deploying the public key to a Windows server is where confident assumptions go to die. For a standard user, the key goes where Unix habits expect: C:\Users\username\.ssh\authorized_keys. But if the account is a member of the Administrators group, sshd ignores that file entirely and reads C:\ProgramData\ssh\administrators_authorized_keys instead. And that file must have a locked-down ACL (SYSTEM and Administrators only), or key authentication silently fails and you fall back to password prompts with no explanation.
There is no ssh-copy-id on Windows. This pair of lines is the equivalent for an admin account, ACL fix included:
$authorizedKey = Get-Content -Path $env:USERPROFILE\.ssh\id_ed25519.pub
ssh username@hostname "powershell Add-Content -Force -Path $env:ProgramData\ssh\administrators_authorized_keys -Value '$authorizedKey'; icacls.exe ""$env:ProgramData\ssh\administrators_authorized_keys"" /inheritance:r /grant ""Administrators:F"" /grant ""SYSTEM:F"""
If key auth ever works from one account and not another on the same box, this split is almost always why. It cost me an afternoon before it became a reflex.
PowerShell Remoting over SSH
This is the part 2006 me was actually asking for. PowerShell 7’s Enter-PSSession, New-PSSession, and Invoke-Command all take SSH parameters, and they work across Windows, Linux, and macOS in any direction:
# Interactive session
Enter-PSSession -HostName server01 -UserName matt
# One-shot command with key auth
Invoke-Command -HostName server01 -UserName matt `
-KeyFilePath ~\.ssh\id_ed25519 `
-ScriptBlock { Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 }
# Persistent session
$s = New-PSSession -HostName server01 -UserName matt
Invoke-Command -Session $s -ScriptBlock { Get-Service sshd }
Unlike plain ssh, this is full object-pipeline remoting: what comes back is deserialized objects, not text. The target machine needs PowerShell registered as an SSH subsystem in C:\ProgramData\ssh\sshd_config:
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs
The progra~1 is not a typo: a Win32-OpenSSH bug rejects subsystem paths containing spaces, so you use the 8.3 short name. Microsoft’s newer suggestion is a symlink next to the ssh config, which survives PowerShell upgrades and spares you explaining progra~1 in a code review:
New-Item -ItemType SymbolicLink -Path C:\ProgramData\ssh\ -Name pwsh.exe `
-Value (Get-Command pwsh.exe).Source
Then the subsystem line becomes Subsystem powershell c:/ProgramData/ssh/pwsh.exe -sshs, no short names required. Either way, Restart-Service sshd afterward: sshd only reads its config at service start.
What Still Does Not Work
Honesty section. SSH remoting is transport-level, so everything WinRM does at the endpoint layer is missing: no Just Enough Administration, no session configurations, no endpoint constraints. Sessions do not load your $PROFILE. sudo does not work inside a remote session to Linux. Key-based sessions have no user credential attached, so they cannot authenticate onward to a file share (the classic second-hop problem persists in a new costume, though Enter-PSSession from inside a session works fine since PowerShell 7.1). Windows OpenSSH also does not support AuthorizedKeysCommand, so you cannot fetch keys dynamically from Active Directory the way you might on Linux, and Entra ID accounts do not do key auth at all.
If you need JEA or constrained endpoints, WinRM is still the answer. For everything else, SSH is simpler, cross-platform, and uses the keys, agents, and config files you already have.
The Arc
In 2006 I administered Windows through a Cygwin compatibility layer and typed PowerShell commands into a shell that would not even echo a prompt back. In 2026 I can sit on a Mac, Enter-PSSession into a Windows Server that shipped with sshd preinstalled, and pipe objects back out, authenticated by an Ed25519 key held in a Windows service, over a channel that will happily negotiate post-quantum crypto.
Twenty years is a long time to wait for someone to take “just use SSH” seriously. But they did, and they did it properly: real OpenSSH, upstream, not an embrace-and-extend imitation. The kid begging in 2006 would not believe it. He would also want to know what took so long.