Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active May 9, 2025 11:30
Show Gist options
  • Save jborean93/7d4cb107fa06251b080fa10ec844893e to your computer and use it in GitHub Desktop.
Save jborean93/7d4cb107fa06251b080fa10ec844893e to your computer and use it in GitHub Desktop.
Windows PowerShell SSH Remoting Stub
<#
.SYNOPSIS
Windows PowerShell SSH Server Subsystem Shim.
.DESCRIPTION
Used as a basic wrapper for Windows PowerShell that allows it to be used as a target for SSH based remoting sessions.
This allows a PowerShell client to target a Windows host through SSH without having PowerShell 7 installed.
.NOTES
This is experimental and used as a POC.
It is not guaranteed to be stable or bug free.
See https://gist.github.com/jborean93/7d4cb107fa06251b080fa10ec844893e?permalink_comment_id=4093819#gistcomment-4093819 for more info.
#>
[CmdletBinding()]
param ()
$ErrorActionPreference = 'Stop'
Add-Type -Namespace PSSSH -Name NativeMethods -MemberDefinition @'
[DllImport("Kernel32.dll", EntryPoint = "GetStdHandle", SetLastError = true)]
private static extern IntPtr GetStdHandleNative(
int nStdHandle);
public static Microsoft.Win32.SafeHandles.SafeFileHandle GetStdHandle(int handleId)
{
IntPtr handle = GetStdHandleNative(handleId);
if (handle == (IntPtr)(-1)) {
throw new System.ComponentModel.Win32Exception();
}
// Std handles should not be freed.
return new Microsoft.Win32.SafeHandles.SafeFileHandle(handle, false);
}
'@
# Cannot use [System.Console]::OpenStandardInput() as it's a ConsoleStream and
# will be unable to read input from an SSH based process.
# https://github.com/PowerShell/PowerShell/issues/14478#issuecomment-1064764004
$stdin = [PSSSH.NativeMethods]::GetStdHandle(-10)
$stdinFS = New-Object System.IO.FileStream $stdin, "Read"
$version = $PSVersionTable.PSVersion
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo.FileName = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$proc.StartInfo.Arguments = "-Version $($version.Major).$($version.Minor) -NoLogo -ServerMode"
$proc.StartInfo.CreateNoWindow = $true
$proc.StartInfo.RedirectStandardInput = $true
$proc.StartInfo.UseShellExecute = $false
$null = $proc.Start()
$utf8 = New-Object System.Text.UTF8Encoding $false
$stdinSR = New-Object System.IO.StreamReader $stdinFS, $utf8
while ($true) {
$line = $stdinSR.ReadLine()
$proc.StandardInput.WriteLine($line)
$proc.StandardInput.Flush()
# Sent by the server to indicate the Runspace Pool is closed.
if ($line.StartsWith("<CloseAck PSGuid='00000000-0000-0000-0000-000000000000' />")) {
break
}
}
$proc | Stop-Process -Force
@keyboardCurry
Copy link

This is absolute gold.

I was pulling my hairs out trying to get PS7 -> PS5.1 working over SSH. Your script seems to work beautifully.

You deserve a cookie, my man.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment