#!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 ';'; }; $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_PROCESSED_INPUT = 0x0001 ENABLE_LINE_INPUT = 0x0002 ENABLE_ECHO_INPUT = 0x0004 ENABLE_WINDOW_INPUT = 0x0008 ENABLE_MOUSE_INPUT = 0x0010 ENABLE_INSERT_MODE = 0x0020 ENABLE_QUICK_EDIT_MODE = 0x0040 ENABLE_EXTENDED_FLAGS = 0x0080 ENABLE_AUTO_POSITION = 0x0100 ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 }; [Flags()] enum ConsoleModeOutputFlags { ENABLE_PROCESSED_OUTPUT = 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 DISABLE_NEWLINE_AUTO_RETURN = 0x0008 }; if (!('NativeConsoleMethods' -as [System.Type])) { Add-Type $consoleModeSource } function rawmode { param ( [validateSet('enable', 'disable')] [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 "enable") { #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 "disable") { #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"; }; # rawmode 'enable'; $consoleid = $args[0]; if ([string]::IsNullOrEmpty($consoleid)) { $consoleid= "" }; $pipeName = "punkshell_ps_consolemode_$consoleid"; "pipename: $pipeName" #$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); $sharedData = [hashtable]::Synchronized(@{}) #TSV $scriptblock = { param($tsv); Add-Type -AssemblyName System.IO.Pipes; #$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( # $pipeName, # [System.IO.Pipes.PipeDirection]::In, # 1, # [System.IO.Pipes.PipeTransmissionMode]::Byte, # [System.IO.Pipes.PipeOptions]::Asynchronous #); ; $serverloop = 0; while ($true) { $pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); $serverloop += 1; $pipeServer.WaitForConnection(); #write-host "Connection established"; $reader = New-Object System.IO.StreamReader($pipeServer); if ($reader -ne $null) { $message = $reader.ReadLine(); $reader.Close(); $reader.Dispose(); if ($message -ne $null) { if ($message -eq "exit") { #write-host "consolemode_server.ps1 exiting"; $tsv.State = "done"; break; } elseif ($message -eq "enableraw") { #write-host "RECEIVED: $message"; $tsv.Message = $message; #$tsv.Message = "$pipeName serverloop: $serverloop"; $tsv.Ping = Get-Date; $msync.Set(); }; } else { # write-host "consolemode_server.ps1 null-msg"; $tsv.State = "done"; break; }; }; $pipeServer.Disconnect(); $pipeServer.Dispose(); }; exit; }; $keepalive_timeout = 20; #number of seconds without ping or other message, after which we terminate the process. try { $syncEvent = New-Object System.Threading.ManualResetEvent($false); $runspace = [runspacefactory]::CreateRunspace(); [void]$runspace.Open(); $runspace.SessionStateProxy.SetVariable("pipeName", $pipeName); $runspace.SessionStateProxy.SetVariable("pipeServer", $null); $runspace.SessionStateProxy.SetVariable("msync", $syncEvent); $powershell = [System.Management.Automation.PowerShell]::Create(); $powershell.Runspace = $runspace; [void]$powershell.Addscript($scriptblock).AddArgument($sharedData); $sharedData.State = "running"; $sharedData.Ping = Get-Date; $asyncResult = $powershell.BeginInvoke(); write-Host "Started named pipe server $pipeName in runspace" $loop = 0; while ($true) { $loop += 1; #write-host "loop $loop"; [void]$syncEvent.WaitOne(($keepalive_timeout * 1000 / 2)); $msg = $sharedData.Message; #Write-Host "$pipeName Last message: $msg"; $sharedData.Message = ""; if ($msg -eq "enableraw") { $null = rawmode 'enable' } elseif ($msg -eq "disableraw") { $null = rawmode 'disable' } #write-host "STATE: $(${sharedData}.State)" if ($(${sharedData}.State) -eq "done") { break; }; $tnow = Get-Date; $elapsed = New-TimeSpan -Start $sharedData.Ping -End $tnow; if ($elapsed.TotalSeconds -lt $keepalive_timeout) { # write-host "ping ok"; } else { write-host "ping stale for pipe $pipeName - exiting"; break; } [void]$syncEvent.Reset(); # start-sleep -Milliseconds 300 }; } finally { # Failing to properly shut down the run process can leave an orphan powershell process # We need to use a client for the named pipe to send an exit message. # Write-Host "terminating process for $pipeName"; try { # Write-Host "creating cli for $pipeName"; $cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); $cli.connect(1000); #Write-Host "sending exit for $pipeName"; $writer = new-object System.IO.StreamWriter($cli); $writer.writeline("exit"); $writer.flush(); #Write-Host "disposing of cli for $pipeName"; $cli.Dispose(); } catch { write-host "error during cli tidyup"; Write-Error "error: $($PSItem.ToString())"; Write-Host "Detailed Exception Message: $($PSItem.Exception.Message)"; }; try { if ($null -ne $runspace) { #Write-Host "closing runspace for $pipeName"; $runspace.Close(); #Write-Host "disposing of runspace for $pipeName"; $runspace.Dispose(); }; } catch { write-host "error during runspace tidyup"; Write-Error "error: $($PSItem.ToString())"; Write-Host "Detailed Exception Message: $($PSItem.Exception.Message)"; } finally { }; try { if ($null -ne $powershell) { #Write-Host "tidying up powershell for $pipeName"; $powershell.dispose(); }; } finally { }; }; write-host "consolemode_server_async.ps1 shutdown for pipe $pipeName"; exit 0;