From f0953207878e7458773b0a45e28b9c9da44658c6 Mon Sep 17 00:00:00 2001 From: "Jason Shirk (POWERSHELL)" Date: Wed, 21 Sep 2016 14:42:16 -0700 Subject: [PATCH 1/3] Fix `powershell -Command -` to not ignore last command If the last command sent via stdin did not have a newline, the command was ignored. This would happen when stdin was closed. --- .../host/msh/ConsoleHostUserInterface.cs | 6 ++++-- test/powershell/Host/ConsoleHost.Tests.ps1 | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 209d2385d7..6c4006e7c8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -1554,7 +1554,7 @@ namespace Microsoft.PowerShell private string ReadLineFromFile(string initialContent) { var sb = new StringBuilder(); - if (initialContent != null) + if (!string.IsNullOrEmpty(initialContent)) { sb.Append(initialContent); sb.Append('\n'); @@ -1566,7 +1566,9 @@ namespace Microsoft.PowerShell if (inC == -1) { // EOF - we return null which tells our caller to exit - return null; + // but only if we don't have any input, we could have + // input and then stdin was closed, but never saw a newline. + return sb.Length == 0 ? null : sb.ToString(); } var c = unchecked((char)inC); diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 671b239282..b756f9bb2e 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -279,7 +279,12 @@ foo "@) $process.StandardOutput.ReadLine() | Should Be "in foo" + # Last command with no newline - should be accepted and + # produce output after closing stdin. + $process.StandardInput.Write('22 + 22') $process.StandardInput.Close() + $process.StandardOutput.ReadLine() | Should Be "44" + EnsureChildHasExited $process } From 1913244fad53144e618a0eb12d1d9791be19aeb6 Mon Sep 17 00:00:00 2001 From: "Jason Shirk (POWERSHELL)" Date: Wed, 21 Sep 2016 15:01:33 -0700 Subject: [PATCH 2/3] Fix `powershell -Command -` handling of backspace PowerShell can be started with input redirected in different scenarios. `powershell -Command -` is one scenario where commands are sent to PowerShell, but this is not a interactive shell scenario, it's really a server to run commands from some other process. `powershell -File -` or just regular redirected input means PowerShell is an interactive shell, but command line editing must be done via the redirected standard input handle instead of interacting directly with a console handle. In this scenario, we want to provide a good editing experience. Today, we provide the bare minimum, supporting backspace but no cursor movement. The bug here is that backspace was treated as a backspace in the server mode but should not have been. --- .../host/msh/ConsoleHostUserInterface.cs | 4 +++- test/powershell/Host/ConsoleHost.Tests.ps1 | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 6c4006e7c8..478d05902e 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -1590,7 +1590,9 @@ namespace Microsoft.PowerShell break; } - if (c == '\b') + // If NoPrompt is true, we are in a sort of server mode where we shouldn't + // do anything like edit the command line - every character is part of the input. + if (c == '\b' && !NoPrompt) { sb.Remove(sb.Length - 1, 1); } diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index b756f9bb2e..63f7e820f9 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -227,6 +227,12 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $process.StandardInput.Write("1+2`n") $process.StandardOutput.ReadLine() | Should Be "PS> 1+2" $process.StandardOutput.ReadLine() | Should Be "3" + + # Backspace should work as expected + $process.StandardInput.Write("1+2`b3`n") + # A real console should render 2`b3 as just 3, but we're just capturing exactly what is written + $process.StandardOutput.ReadLine() | Should Be "PS> 1+2`b3" + $process.StandardOutput.ReadLine() | Should Be "4" $process.StandardInput.Close() $process.StandardOutput.ReadToEnd() | Should Be "PS> " EnsureChildHasExited $process @@ -279,6 +285,10 @@ foo "@) $process.StandardOutput.ReadLine() | Should Be "in foo" + # Backspace sent through stdin should be in the final string + $process.StandardInput.Write("`"a`bc`".Length`n") + $process.StandardOutput.ReadLine() | Should Be "3" + # Last command with no newline - should be accepted and # produce output after closing stdin. $process.StandardInput.Write('22 + 22') From 07b0964be402dc99defe1eacc4ef9bbcf847b3fa Mon Sep 17 00:00:00 2001 From: "Jason Shirk (POWERSHELL)" Date: Mon, 26 Sep 2016 10:30:42 -0700 Subject: [PATCH 3/3] Fix hang when reading stdin from ruby This is a workaround for a full clr issue that causes a hang when calling PowerShell from ruby. They use named pipes instead of anonymous pipes, and for some reason that triggers the hang. --- .../host/msh/ConsoleControl.cs | 3 +++ .../host/msh/ConsoleHost.cs | 19 +++++++++++++++++++ .../host/msh/ConsoleHostUserInterface.cs | 7 ++++--- .../host/msh/Executor.cs | 3 +-- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs index cbf3bc95aa..4143670932 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs @@ -3467,6 +3467,9 @@ namespace Microsoft.PowerShell [DllImport(PinvokeDllNames.GetLargestConsoleWindowSizeDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern COORD GetLargestConsoleWindowSize(NakedWin32Handle consoleOutput); + [DllImport(PinvokeDllNames.GetStdHandleDllName, SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern IntPtr GetStdHandle(int handleId); + [DllImport(PinvokeDllNames.ReadConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool ReadConsole ( diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 71838ee2c7..2b3f195c22 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -2868,6 +2868,25 @@ namespace Microsoft.PowerShell private System.Threading.Thread _breakHandlerThread; private bool _isDisposed; internal ConsoleHostUserInterface ui; + +#if CORECLR + internal Lazy ConsoleIn { get; } = new Lazy(() => Console.In); +#else + internal Lazy ConsoleIn { get; } = new Lazy(() => + { + // This is a workaround for a full clr issue that causes a hang when calling PowerShell from ruby. + // They use named pipes instead of anonymous pipes, and for some reason that triggers a hang + // reading from Console.In. + var inputHandle = ConsoleControl.NativeMethods.GetStdHandle(-10); + var s = new FileStream(new ConsoleHandle(inputHandle, false), FileAccess.Read); + + uint codePage = (uint) ConsoleControl.NativeMethods.GetConsoleCP(); + Encoding encoding = Encoding.GetEncoding((int) codePage); + + return TextReader.Synchronized(new StreamReader(s, encoding, false)); + }); +#endif + private string _savedWindowTitle = ""; private Version _ver = PSVersionInfo.PSVersion; private int _exitCodeFromRunspace; diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 478d05902e..83ce13c7be 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -1560,9 +1560,10 @@ namespace Microsoft.PowerShell sb.Append('\n'); } + var consoleIn = _parent.ConsoleIn.Value; while (true) { - var inC = Console.In.Read(); + var inC = consoleIn.Read(); if (inC == -1) { // EOF - we return null which tells our caller to exit @@ -1577,10 +1578,10 @@ namespace Microsoft.PowerShell if (c == '\r') { // Treat as newline, but consume \n if there is one. - if (Console.In.Peek() == '\n') + if (consoleIn.Peek() == '\n') { if (!NoPrompt) Console.Out.Write('\n'); - Console.In.Read(); + consoleIn.Read(); } break; } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs index fa8f2af8c6..d7e09f2993 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs @@ -212,8 +212,7 @@ namespace Microsoft.PowerShell if ((options & ExecutionOptions.ReadInputObjects) > 0 && Console.IsInputRedirected) { // read input objects from stdin - - WrappedDeserializer des = new WrappedDeserializer(_parent.InputFormat, "Input", Console.In); + WrappedDeserializer des = new WrappedDeserializer(_parent.InputFormat, "Input", _parent.ConsoleIn.Value); while (!des.AtEnd) { object o = des.Deserialize();