In unix world we have always been able to send character strings between terminal windows using the /dev/ttyS
serial devices or the /dev/pty
pseudo-terminals. To do that is trivial once you know your tty/pty number, and could use:
# On /dev/pty1 write:
echo -e "\nHello! \n" >/dev/pty0
# On /dev/pty0, receive:
Hello!
(There is a bug in the Cygwin/Bash version that interferes with the character \n
and whatever is in front, unless its a space.)
This even works in MSYS/Cygwin.
Is there a way to accomplish a way to do the same (in Windows) between powershell terminal windows?
I have seen that you can send strings using the COM
serial devices, using something like:
echo "Hello!" > \\.\COM3\
But AFAIK, the Windows terminal shell doesn't have an associated COM port, nor anything else obvious or accessible.
Apparently the COM ports are set in the registry at:
HKLM:\SYSTEM\CurrentControlSet\Control\COM Name Arbiter\Devices
References:
The closest I could think is to use NamedPipeServerStream
and NamedPipeClientStream
as suggested in this answer, however the difference here is that your server is started on a different thread and writes to your console using PSConsoleReadLine.Insert
, this provides similar functionality as what you have in the Linux word.
Here you can see how to use these functions:
You can also pipe to the writing function, so something like this can work:
PS \> Get-Content foo.txt | pipemsg theDestinationPipe
You should note using PSConsoleReadLine.Insert
like this is a bit wonky and outside the supported realm.
Lastly, the Start-NamedPipeServer
function outputs an object that can be used as argument to Remove-NamedPipeServer
. Once you're done using your server you can dispose it like this:
# create
$server = Start-NamedPipeServer myServer
# dispose
$server | Remove-NamedPipeServer
Here is the function definitions:
function Start-NamedPipeServer {
[Alias('pipeserver')]
param(
[Parameter(Mandatory)]
[string] $Name
)
if ([System.IO.File]::Exists("\\.\pipe\$Name")) {
$err = [System.Management.Automation.ErrorRecord]::new(
[System.IO.IOException]::new("A pipe already exists with name '$Name'."),
'PipeExists',
[System.Management.Automation.ErrorCategory]::OpenError,
$null)
$PSCmdlet.ThrowTerminatingError($err)
}
$script = {
while (-not $token.IsCancellationRequested) {
try {
$pipe = [System.IO.Pipes.NamedPipeServerStream]::new(
$Name, [System.IO.Pipes.PipeDirection]::InOut, 1,
[System.IO.Pipes.PipeTransmissionMode]::Byte,
[System.IO.Pipes.PipeOptions]::Asynchronous)
$task = $pipe.WaitForConnectionAsync($token)
while (-not $task.AsyncWaitHandle.WaitOne(200)) { }
$null = $task.GetAwaiter().GetResult()
$sr = [System.IO.StreamReader]::new($pipe)
while ($null -ne ($msg = $sr.ReadLine())) {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($msg + "`n")
}
}
catch [System.OperationCanceledException] {
# ignore
}
finally {
if ($pipe) { $pipe.Dispose() }
if ($sr) { $sr.Dispose() }
}
}
}
$src = [System.Threading.CancellationTokenSource]::new()
$ps = [powershell]::Create().AddScript($script)
$ps.Runspace.SessionStateProxy.SetVariable('Name', $Name)
$ps.Runspace.SessionStateProxy.SetVariable('token', $src.Token)
[pscustomobject]@{
TokenSource = $src
Instance = $ps
Async = $ps.BeginInvoke()
}
}
function Write-PipeMessage {
[Alias('pipemsg')]
param(
[Parameter(Mandatory)]
[string] $Name,
[Parameter(ValueFromPipeline, ValueFromRemainingArguments)]
[string[]] $Message
)
begin {
try {
$pipe = [System.IO.Pipes.NamedPipeClientStream]::new($Name)
$task = $pipe.ConnectAsync()
while (-not $task.Wait(200)) { }
$sw = [System.IO.StreamWriter]::new($pipe)
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
}
process {
foreach ($msg in $Message) {
$sw.WriteLine($msg)
}
}
end {
$sw.Dispose()
$pipe.Dispose()
}
}
function Remove-NamedPipeServer {
param([Parameter(Mandatory, ValueFromPipeline)] $Server)
process {
try {
$Server.TokenSource.Cancel()
$Server.TokenSource.Dispose()
$Server.Instance.EndInvoke($Server.Async)
if ($Server.Instance.HadErrors) {
foreach ($err in $Server.Instance.Streams.Error) {
$PSCmdlet.WriteError($err)
}
}
$Server.Instance.Dispose()
}
catch {
$PSCmdlet.WriteError($_)
}
}
}