#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! ; if ($PSVersionTable.PSVersion.Major -le 5) { # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? ; $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; }; $helper = @' using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Management.Automation.Runspaces; public class RunspacedDelegateFactory { public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace) { Action setRunspace = () => Runspace.DefaultRunspace = runspace; return ConcatActionToDelegate(setRunspace, _delegate); } private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments) { var invokeMethod = _delegate.GetType().GetMethod("Invoke"); return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments); } public static Delegate ConcatActionToDelegate(Action a, Delegate d) { var parameters = d.GetType().GetMethod("Invoke").GetParameters() .Select(p => Expression.Parameter(p.ParameterType, p.Name)) .ToArray(); Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters)); var lambda = Expression.Lambda(d.GetType(), body, parameters); var compiled = lambda.Compile(); return compiled; } } '@ add-type -TypeDefinition $helper #region console $consoleModeSource = @" using System; using System.Runtime.InteropServices; public class NativeConsoleMethods { [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr GetStdHandle(int handleId); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); public static uint GetConsoleMode(bool input = false) { var handle = GetStdHandle(input ? -10 : -11); uint mode; if (GetConsoleMode(handle, out mode)) { return mode; } return 0xffffffff; } public static uint SetConsoleMode(bool input, uint mode) { var handle = GetStdHandle(input ? -10 : -11); if (SetConsoleMode(handle, mode)) { return GetConsoleMode(input); } return 0xffffffff; } } "@ ; [Flags()] enum ConsoleModeInputFlags { ENABLE_LINE_INPUT = 0x0002 ENABLE_ECHO_INPUT = 0x0004 }; if (!('NativeConsoleMethods' -as [System.Type])) { Add-Type $consoleModeSource } function psmain { param ( [validateSet('enableRaw', 'disableRaw')] [string]$Action ); # $inputflags = Get-ConsoleMode -StandardInput; if (!('NativeConsoleMethods' -as [System.Type])) { Add-Type $consoleModeSource } $inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); $resultflags = $inputflags; if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { #cooked mode $initialstate = "cooked"; if ($action -eq "enableraw") { #disable cooked flags $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); $adjustedflags = $inputflags -band ($disable); $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); } } else { #raw mode $initialstate = "raw"; if ($action -eq "disableraw") { #set cooked flags $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); } } #return in format that can act as a tcl dict #write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; }; # psmain 'enableRaw'; #endregion console $consoleid = $args[0]; if ([string]::IsNullOrEmpty($consoleid)) { $consoleid= "" }; $pipeName = "punkshell_ps_consolemode_$consoleid"; "pipename: $pipeName"; $pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( $pipeName, [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Byte, [System.IO.Pipes.PipeOptions]::Asynchronous ); #$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); ; # Define the callback function for when a client connects $callback = [System.AsyncCallback]{ param($asyncResult); $se = $asyncResult.AsyncState.Sync; $ps = $asyncResult.AsyncState.Pipeserver; $pn = $asyncResult.AsyncState.Pipename; Write-Host "Client connected - $pn"; # End the asynchronous wait operation ; $ps.EndWaitForConnection($asyncResult); #?? ; # You can now perform read/write operations with the client # For example, create a StreamReader and StreamWriter ; $streamReader = New-Object System.IO.StreamReader($ps); #$streamWriter = New-Object System.IO.StreamWriter($pipeServer); #$streamWriter.AutoFlush = $true; try { $message = $streamReader.ReadLine(); Write-Host "Received: $message"; ; #$asyncResult.Message = $message; ; } catch { Write-Error "Error during communication: $($_.Exception.Message)"; } finally { # sever connection with client but keep the named pipe if ($streamReader -ne $null) { Write-Host "streamreader closing"; $streamReader.Close(); Write-Host "streamreader closed"; } Write-Host "Client disconnecting. $pn"; $ps.Disconnect(); Write-Host "Client disconnected. $pn"; #$ps.Disconnect(); #[System.Console]::Out.Flush(); ; }; write-host "HERE"; $se.set(); # Optionally, you can call BeginWaitForConnection again to listen for another client # if your server is designed for multiple connections over time. # $pipeServer.BeginWaitForConnection($callback, $null) $ps.BeginWaitForConnection($callback, $null) ; }; $syncEvent = New-Object System.Threading.ManualResetEvent($false); $runspacedDelegate = [RunspacedDelegateFactory]::NewRunspacedDelegate($callback, [Runspace]::DefaultRunspace); $loop = 0; while ($loop -lt 15) { Write-Host "Waiting for client connection on pipe '$pipeName'..."; # Begin the asynchronous wait for a client connection ; $state = [PSCustomObject]@{ Loop = $loop Sync = $SyncEvent Pipeserver = $pipeServer Pipename = $pipename Message = "" }; $x = $pipeServer.BeginWaitForConnection($runspacedDelegate, $state ); $syncEvent.WaitOne(10000); # $x | Get-Member | write-host ; write-host "msg: $(${x}.AsyncState.Message)" if ($x.IsCompleted) { write-host "completed"; }; $SyncEvent.reset(); $loop += 1; #$cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); #$cli.w } # Keep the script running to allow the asynchronous operation to complete # In a real-world scenario, you might have a loop or other logic here. #Read-Host "Press Enter to exit the server." # Clean up $pipeServer.Dispose();