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 209d2385d7..83ce13c7be 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -1554,19 +1554,22 @@ 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'); } + 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 - 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); @@ -1575,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; } @@ -1588,7 +1591,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/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(); diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 671b239282..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,7 +285,16 @@ 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') $process.StandardInput.Close() + $process.StandardOutput.ReadLine() | Should Be "44" + EnsureChildHasExited $process }