Reputation: 1608
I am expierencing an issue with a powershell script which doesn't work whereas dotnet application with the same commands works. I believe the problem is related to encoding, I think in powershell the encoding is different which causes some non-transparent problems with a byte-object-marker or decoding/encoding itself.
A little bit background, I am using proxy command in ssh to automate some steps of setting up a tunnel. I have a working prototype that is using a small dotnet based executable that forwards stdin and stdout to a tcp socket. However preferable I'd like to use vanilla powershell script if possible.
The information that should be forwarded over stdin/stdout should be preserved as-is without any encoding conversion, and when I use a dotnet application it fully works, but when I use the powershell script with equavelent code ssh basically reports sometimes the packet sizes are different / unexpected big.
Anybody have any idea what type of call I could do to reset powershell to not encode stdin/stdout but map the data 1:1?
Host XXYYZZWW
User server
HostName 127.0.0.1
Port 2222
StrictHostKeyChecking no
NumberOfPasswordPrompts 0
PasswordAuthentication no
BatchMode yes
IdentityFile ~/.ssh/id_rsa
ProxyCommand powershell -File D:/Workspaces/liquid/maintenancw/rustdesk-ssh.ps1 -LocalPort 2222 -RemotePort 22 -RemoteHost 127.0.0.1 -RustdeskId XXYYZZWW
param ($RustdeskId, $IdentityFile, $User, $LocalPort, $RemotePort, $RemoteHost, $RemoteUser)
$def = @"
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System;
public class MyClass {
public void Run() {
var tcpClient = new System.Net.Sockets.TcpClient("localhost", 2222);
tcpClient.NoDelay = true;
var tcpStream = tcpClient.GetStream();
var stdin = System.Console.OpenStandardInput();
var stdout = System.Console.OpenStandardOutput();
System.Threading.Tasks.Task.WaitAll(stdin.CopyToAsync(tcpStream, 1024), tcpStream.CopyToAsync(stdout, 1024));
}
}
"@
Add-Type -TypeDefinition $def
$obj = New-Object MyClass
$obj.Run()
Upvotes: 1
Views: 241
Reputation: 439812
What you're trying to do can only be implemented in a PowerShell script if:
Either:
If you provide stdin input to it from outside PowerShell, such as calling it from cmd.exe
(on Windows) or bash
(in Unix-like environments) via the PowerShell CLI (powershell.exe
for Windows PowerShell, pwsh
for PowerShell (Core) 7)
From inside PowerShell, in Windows PowerShell and up to PowerShell 7.3.x, you won't be able to supply raw byte data to your invoked-via-a-child-process script (see below for PowerShell 7.4+).
cmd.exe
, assuming that some.exe
provides the input to your script:cmd /c 'some.exe | powershell -file yourscript.ps1'
Or: In PowerShell 7.4+ only, if you:
(a) call your script via a child process, using pwsh
.ps1
extension.and (b) if the command providing the input data is either an external (native) program, or, if you use a PowerShell command, if you make that command provide the data in the form of a [byte[]
array (or a stream of [byte]
instances).
See below for background information.
Via PowerShell's CLI (powershell.exe
for Windows PowerShell, pwsh
for PowerShell (Core) 7), i.e. when PowerShell is called from the outside (even in PowerShell 7.4.x):
Stdin input to the CLI, as reflected in the automatic $input
variable, is invariably interpreted as text.
chcp.com
.Stdout output from the CLI is invariably converted to text, using plain-text output by default; via -OutputFormat xml
(-of xml
), you can request CLIXML output, an XML-based serialization format specific to PowerShell that preserves the original data types within limitations.
[Console]::OutputEncoding
, which defaults to the active code page of the current console / terminal, as reflected in the output from chcp.com
(but can be changed in-session).However, you can work with binary data (raw bytes), if you use .NET APIs directly, as shown in your question ([System.Console]::OpenStandardInput()
and [System.Console]::OpenStandardOutput()
).
Inside a PowerShell session:
Windows PowerShell and PowerShell (Core) 7 up to version 7.3.x, invariably only "speak text" when sending data to an external (native) program('s stdin stream) and when receiving data from it(s stdout stream).
Data sent (piped to) to an external program is encoded as text, based on the $OutputEncoding
preference variable
Data received is decoded based on the encoding stored in [Console]::OutputEncoding
Ensuring that raw bytes are passed between external programs therefore requires a workaround, which on Windows means calling via cmd.exe /c
: see this answer.
In PowerShell 7 from version 7.4 on, raw bytes streams now are supported, with limitations:
You may send (pipe) [byte[]]
data to an external program, which causes the latter to receive the raw bytes contained in this byte array (you may also pipe the data byte by byte ([byte]
), but that performs worse).
You may receive raw byte data from an external program if you redirect it to a file using >
, i.e. via a redirection - there's no direct way to receive the data as a [byte[]]
array in memory.
Additionally, the pipeline now implicitly acts as a raw byte conduit between external programs. That is, if data is piped from one external program to another, the latter receives the raw bytes.
Upvotes: 0