You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

263 lines
9.3 KiB

#!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= "<punkshell_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;