|
#Exposes the Powershell Remoting Protocol via a TCP Port |
|
using namespace System.Management.Automation.Runspaces |
|
using namespace Renci.SshNet |
|
using namespace System.Net.Sockets |
|
using namespace System.Threading.Tasks |
|
using namespace System.IO.Pipes |
|
using namespace System.IO |
|
[CmdletBinding(DefaultParameterSetName = 'Server')] |
|
param ( |
|
[Parameter(Position = 0, ParameterSetName = 'Client')][String]$ComputerName = 'localhost', |
|
[Parameter(ParameterSetName = 'Client')][String]$PipeName = $("PSHost.$ComputerName.$(New-Guid)"), |
|
[Int]$Port = 7073, |
|
[Parameter(ParameterSetName = 'Server')][ipaddress]$ListenAddress = '127.0.0.1', |
|
[Parameter(ParameterSetName = 'Server')][Switch]$AllowRemoteConnections, |
|
[Parameter(ParameterSetName = 'Server')][Switch]$NoSSHRelay, |
|
[Parameter(ParameterSetName = 'Server')][String]$SSHRelayHost = 'pwsh.link', |
|
[Parameter(ParameterSetName = 'Server')][Int]$SSHRelayPort = 2222 |
|
) |
|
|
|
if ($AllowRemoteConnections) { $ListenAddress = '0.0.0.0' } |
|
|
|
function Get-PSRemotingNamedPipe ($ID = $PID) { |
|
<# |
|
.SYNOPSIS |
|
Gets the PSRemoting autogenerated named pipe of the specified process |
|
.NOTES |
|
Reimplementation of internal powershell method NamedPipeUtils.CreateProcessPipeName |
|
#> |
|
$process = Get-Process -Id $ID -ErrorAction Stop |
|
$processStartTime = $process.starttime.tofiletime() |
|
|
|
$processStartId = if ($PSEdition -eq 'Desktop' -or $isWindows) { |
|
$processStartTime |
|
} else { |
|
$processStartTime.ToString('X8').Substring(1, 8) |
|
} |
|
|
|
# $pipeNamePrefix = 'PSHost' |
|
# if (-not ($PSEdition -eq 'Desktop' -or $isWindows)) { |
|
# $pipeNamePrefix = "CoreFxPipe_$pipeNamePrefix" |
|
# } |
|
|
|
return (@( |
|
'PSHost' |
|
$processStartID |
|
$process.Id |
|
'DefaultAppDomain' |
|
$process.ProcessName |
|
) -join '.').ToString([cultureinfo]::InvariantCulture) |
|
} |
|
|
|
function Connect-PSRemotingNamedPipe ([String]$Name = (Get-PSRemotingNamedPipe), [String]$ComputerName = '.') { |
|
#Connect the debug pipe |
|
$pipeclient = [NamedPipeClientStream]::new( |
|
$ComputerName, #string serverName |
|
$Name, #string pipeName |
|
[PipeDirection]::InOut, #PipeDirection direction |
|
[PipeOptions]::Asynchronous #PipeOptions options |
|
) |
|
Write-Host -NoNewline -Fore Cyan "Connecting to Powershell Named Pipe $Name..." |
|
$pipeclient.Connect() |
|
Write-Host -Fore Green 'OK!' |
|
return $pipeClient |
|
} |
|
|
|
function Start-PSRemotingTCPListener ([Int]$Port = 7073, [ipaddress]$ListenAddress = '127.0.0.1') { |
|
#Connect the tcp listener. 7073 = ps in hex :) |
|
$tcpListener = [TcpListener]::new($ListenAddress, $port) |
|
$tcpListener.Start() |
|
Write-Host -NoNewline -Fore Cyan "Waiting for connection to ${listenAddress}:$port..." |
|
$tcpClient = $tcpListener.AcceptTcpClient() |
|
Write-Host -Fore Green 'OK!' |
|
return $tcpClient |
|
} |
|
|
|
function Connect-PSRemotingTCPClient ([string]$ComputerName = 'localhost', [Int]$Port = 7073) { |
|
#Connect the tcp listener. 7073 = ps in hex :) |
|
return [TcpClient]::new($ComputerName, $port) |
|
} |
|
|
|
function Start-PSRemotingPipe ($PipeName) { |
|
$namedPipeClient = [NamedPipeServerStream]::new( |
|
$PipeName, |
|
[PipeDirection]::InOut, |
|
1, #maxNumberofServerInstances |
|
[PipeTransmissionMode]::Message, |
|
[PipeOptions]::Asynchronous |
|
) |
|
Write-Host -NoNewline -Fore Cyan "Waiting for Enter-PSHostProcess -CustomPipeName $PipeName ..." |
|
$namedPipeClient.WaitForConnection() |
|
#Enter-PSHostProcess disconnects the first connection for some reason, so we wait for the reconnect callback |
|
$namedPipeClient.Disconnect() |
|
$namedPipeClient.WaitForConnection() |
|
Write-Host -Fore Green 'OK!' |
|
return $namedPipeClient |
|
} |
|
|
|
|
|
function Join-Stream { |
|
param ( |
|
#Provide an array of two streams to join together |
|
[ValidateCount(2, 2)][Stream[]]$Stream, |
|
#Wait for one of the streams to complete or disconnect. The script will return the first stream to disconnect |
|
[Switch]$Wait |
|
) |
|
$copyStreamTasks = @( |
|
$Stream[0].CopyToAsync($Stream[1]) |
|
$Stream[1].CopyToAsync($Stream[0]) |
|
) |
|
if ($Wait) { |
|
$completedTaskIndex = [Task]::WaitAny($copyStreamTasks) |
|
return $copyStreamTasks[$completedTaskIndex] |
|
} else { |
|
$copyStreamTasks |
|
} |
|
} |
|
|
|
function Connect-SSHRelayServer { |
|
param ( |
|
[ValidateNotNullOrEmpty]$NoSSHForward = $NoSSHForward, |
|
[ValidateNotNullOrEmpty]$SSHHost = $SSHHost, |
|
[ValidateNotNullOrEmpty]$SSHPort = $SSHPort |
|
) |
|
|
|
$tempPath = [io.path]::GetTempPath() |
|
[String[]]$SSHAssembly = 'Renci.SshNet.dll', 'SshNet.Security.Cryptography.dll' | ForEach-Object {Join-Path $tempPath $PSItem} |
|
$SSHZipPath = join-path $tempPath 'Renci.SSHNet.Zip' |
|
if ($false -in (Test-Path $SSHAssembly)) { |
|
[Net.Webclient]::new().DownloadFile( |
|
"https://gist.githubusercontent.com/JustinGrote/866536458bfa097cb18a4b82181e5f16/raw/Renci.SSHNet.zip", |
|
$SSHZipPath |
|
) |
|
Expand-Archive $SSHZipPath -DestinationPath $tempPath -Verbose |
|
} |
|
|
|
Add-Type -Path $SSHAssembly -ErrorAction Stop |
|
|
|
try { |
|
$sshClient = [SSHClient]::new( |
|
'pwsh.link', #string host |
|
2222, #int port |
|
'whoever', #string username |
|
'whatever' #string password |
|
) |
|
$sshClient.KeepAliveInterval = [timespan]::FromSeconds(1) |
|
$errorWatcher = Register-ObjectEvent -InputObject $SSHClient -EventName ErrorOccurred -Action { Write-Warning ($EventArgs.exception) } |
|
$sshClient.Connect() |
|
$sshClient.AddForwardedPort( |
|
[Renci.SshNet.ForwardedPortRemote]::new( |
|
'127.0.0.1', |
|
40022, |
|
'127.0.0.1', |
|
2222 |
|
) |
|
) |
|
$sshclient.forwardedports[0].start() |
|
Read-Host 'enter to stop' |
|
} finally { |
|
$sshClient.Disconnect() |
|
Unregister-Event -SourceIdentifier $errorWatcher.name |
|
} |
|
} |
|
|
|
#region main |
|
do { |
|
try { |
|
if ($PSCmdlet.ParameterSetName -eq 'Client') { |
|
$tcpClient = Connect-PSRemotingTCPClient -ComputerName $ComputerName -Port $Port |
|
$pipeClient = Start-PSRemotingPipe -PipeName $PipeName |
|
} elseif ($PSCmdlet.ParameterSetName -eq 'Server') { |
|
$pipeClient = Connect-PSRemotingNamedPipe |
|
$tcpClient = Start-PSRemotingTCPListener |
|
} else { throw [System.NotSupportedException]"Unknown Parameter Set $($PSCmdlet.ParameterSetName)" } |
|
|
|
$firstClosedStream = Join-Stream -Wait $pipeClient, $tcpClient.GetStream() |
|
if (-not $firstClosedStream.IsCompletedSuccessfully) { |
|
throw $firstClosedStream.Exception |
|
} |
|
} catch { Write-Error $PSItem } finally { |
|
$pipeClient.Close() |
|
$pipeClient.Dispose() |
|
$tcpClient.Close() |
|
$tcpClient.Dispose() |
|
if ($tcpListener) { $tcpListener.Stop() } |
|
} |
|
} while ($PSCmdlet.ParameterSetName -eq 'Server') |
|
#endregion main |