Last active
July 27, 2024 19:38
-
-
Save jborean93/7d4cb107fa06251b080fa10ec844893e to your computer and use it in GitHub Desktop.
Windows PowerShell SSH Remoting Stub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Currently PSRemoting over PowerShell requires a PowerShell (Core) v6+ to be installed on the remote host and configured as a subsystem as per https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/ssh-remoting-in-powershell-core?view=powershell-7.2#install-the-ssh-service-on-a-windows-computer. While it is possible to get the subsystem to start Windows PowerShell by specifying
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Version 5.1 -NoLogo -ServerMode
as the subsystem entry there is a unique combination of scenarios that stop this from just working PowerShell/PowerShell#14478 (comment).To bypass this problem I've created a basic PowerShell script that reads from the raw stdin pipe through a FileStream rather than through the
ConsoleStream
that[Console]::OpenStandardInput()
returns as theConsoleStream
has the blocking problem documented in https://sourceware.org/legacy-ml/cygwin/2013-12/msg00345.html. As it's bypassing this problem the process is able to read the stdin from the SSH pipe and it just passes along the data to the Windows PowerShell server process it spawns. There's no need to manually close the process as SSH will kill and child processes once it exits for you.To get this working place this script in a folder on your host, in my example I placed it in
C:\ProgramData\ssh\win_powershell_ssh.ps1
. In theC:\ProgramData\ssh\sshd_config
file add the following entryReload
sshd
withRestart-Service -Name sshd
and the changes are applied.Connecting to the WinPS instance is as simple as adding
-Subsystem win_powershell
to the PSRemoting cmdlets like so;You can also set the label to be
powershell
if you wish that to be the default configuration that PowerShell targets.Please note this is in no way supported or endorsed by Microsoft or the PowerShell. I was curious as to how to get this working and this was the end result.