PipeExec and .NET 4.0 Console Input

When using the PipeExec component and attempting to send data to a .NET 4.0 or up Console application the input is not recognized by the .NET Console Application. Previous versions of .NET can read the input from a Pipe.

This is an issue with the input processing in .NET 4.0 which limits the ability to read from pipes for input. As an alternative the class below allows reading from the input stream using native methods which do not have this restriction.

In your .NET 4.0 Console Application create a MyConsole class as defined in the example class below. Usage of the new class is the same as the regular Console class. For instance:

string input = ""; while ((input = MyIO.MyConsole.In.ReadLine()) != null) { Console.WriteLine("Echo back: " + input); }

Note: The MyConsole class uses P/Invoke and the Allow unsafe code setting option must be checked in the Build section of the project options.

MyConsole Class:

using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; namespace MyIO { public static class MyConsole { internal sealed class MyConsoleStream : Stream { internal const int DefaultBufferSize = 128; // From winerror.h private const int ERROR_BROKEN_PIPE = 109; // ERROR_NO_DATA ("The pipe is being closed") is returned when we write to // a console that is closing. private const int ERROR_NO_DATA = 232; private SafeFileHandle _handle; private bool _canRead; private bool _canWrite; internal MyConsoleStream(SafeFileHandle handle, FileAccess access) { _handle = handle; _canRead = access == FileAccess.Read; _canWrite = access == FileAccess.Write; } public override bool CanRead { get { return _canRead; } } public override bool CanWrite { get { return _canWrite; } } public override bool CanSeek { get { return false; } } public override long Length { get { throw new NotImplementedException(); } } public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void Flush() { } public override long Seek(long offset, SeekOrigin origin) { return 0; } public override void SetLength(long value) { } protected override void Dispose(bool disposing) { // We're probably better off not closing the OS handle here. First, // we allow a program to get multiple instances of __ConsoleStreams // around the same OS handle, so closing one handle would invalidate // them all. Additionally, we want a second AppDomain to be able to // write to stdout if a second AppDomain quits. if (_handle != null) { _handle = null; } _canRead = false; _canWrite = false; base.Dispose(disposing); } public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException("buffer"); if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException((offset < 0 ? "offset" : "count"), "ArgumentOutOfRange_NeedNonNegNum"); if (buffer.Length - offset < count) throw new ArgumentException("Argument_InvalidOffLen"); if (!_canRead) { throw new InvalidOperationException("Can not read."); } int errorCode = 0; int result = ReadFileNative(_handle, buffer, offset, count, 0, out errorCode); if (result == -1) { throw new IOException("read error", errorCode); } return result; } public override void Write(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException("buffer"); if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException((offset < 0 ? "offset" : "count"), "ArgumentOutOfRange_NeedNonNegNum"); if (buffer.Length - offset < count) throw new ArgumentException("Argument_InvalidOffLen"); if (!_canWrite) { throw new InvalidOperationException("Can not write."); } int errorCode = 0; int result = WriteFileNative(_handle, buffer, offset, count, 0, out errorCode); if (result == -1) { //BCLDebug.ConsoleError("__ConsoleStream::Write: throwing on error. Error code: "+errorCode+" 0x"+errorCode.ToString("x")+" handle: "+_handle.ToString()); throw new IOException("write error", errorCode); } return; } // P/Invoke wrappers for writing to and from a file, nearly identical // to the ones on FileStream. These are duplicated to save startup/hello // world working set. unsafe private static int ReadFileNative(SafeFileHandle hFile, byte[] bytes, int offset, int count, int mustBeZero, out int errorCode) { // Don't corrupt memory when multiple threads are erroneously writing // to this stream simultaneously. if (bytes.Length - offset < count) throw new IndexOutOfRangeException("IndexOutOfRange_IORaceCondition"); // You can't use the fixed statement on an array of length 0. if (bytes.Length == 0) { errorCode = 0; return 0; } int r; int numBytesRead; fixed (byte* p = bytes) { r = ReadFile(hFile, p + offset, count, out numBytesRead, IntPtr.Zero); } if (r == 0) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == ERROR_BROKEN_PIPE) { // A pipe into stdin was closed. Not an error, but EOF. return 0; } return -1; } else errorCode = 0; return numBytesRead; } unsafe private static int WriteFileNative(SafeFileHandle hFile, byte[] bytes, int offset, int count, int mustBeZero, out int errorCode) { // You can't use the fixed statement on an array of length 0. if (bytes.Length == 0) { errorCode = 0; return 0; } int numBytesWritten = 0; int r; fixed (byte* p = bytes) { r = WriteFile(hFile, p + offset, count, out numBytesWritten, IntPtr.Zero); } if (r == 0) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == ERROR_NO_DATA || errorCode == ERROR_BROKEN_PIPE) return 0; return -1; } else { errorCode = 0; } return numBytesWritten; } // The P/Invoke declarations for ReadFile and WriteFile are here for a reason! This prevents us from loading several classes // in the trivial hello world case. [DllImport("kernel32.dll", SetLastError = true)] unsafe private static extern int ReadFile(SafeFileHandle handle, byte* bytes, int numBytesToRead, out int numBytesRead, IntPtr mustBeZero); [DllImport("kernel32.dll", SetLastError = true)] unsafe static internal extern int WriteFile(SafeFileHandle handle, byte* bytes, int numBytesToWrite, out int numBytesWritten, IntPtr mustBeZero); } private const int _DefaultConsoleBufferSize = 256; private static TextReader _in; private static TextWriter _out; private static TextWriter _error; // Private object for locking instead of locking on a public type for SQL reliability work. private static object s_InternalSyncObject; private static object InternalSyncObject { get { if (s_InternalSyncObject == null) { object o = new object(); Interlocked.CompareExchange(ref s_InternalSyncObject, o, null); } return s_InternalSyncObject; } } // About reliability: I'm not using SafeHandle here. We don't // need to close these handles, and we don't allow the user to close // them so we don't have many of the security problems inherent in // something like file handles. Additionally, in a host like SQL // Server, we won't have a console. private static IntPtr _consoleInputHandle; private static IntPtr _consoleOutputHandle; const int STD_OUTPUT_HANDLE = -11; const int STD_INPUT_HANDLE = -10; const int STD_ERROR_HANDLE = -12; [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", SetLastError = true)] internal static extern uint GetConsoleCP(); [DllImport("kernel32.dll", SetLastError = true)] static extern uint GetConsoleOutputCP(); private static IntPtr ConsoleInputHandle { get { if (_consoleInputHandle == IntPtr.Zero) { _consoleInputHandle = GetStdHandle(STD_INPUT_HANDLE); } return _consoleInputHandle; } } private static IntPtr ConsoleOutputHandle { get { if (_consoleOutputHandle == IntPtr.Zero) { _consoleOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE); } return _consoleOutputHandle; } } public static TextWriter Error { get { // Hopefully this is inlineable. if (_error == null) InitializeStdOutError(false); return _error; } } public static TextReader In { get { // Because most applications don't use stdin, we can delay // initialize it slightly better startup performance. if (_in == null) { lock (InternalSyncObject) { if (_in == null) { // Set up Console.In Stream s = OpenStandardInput(_DefaultConsoleBufferSize); TextReader tr; if (s == Stream.Null) tr = StreamReader.Null; else { // Hopefully Encoding.GetEncoding doesn't load as many classes now. Encoding enc = Encoding.GetEncoding((int)GetConsoleCP()); tr = TextReader.Synchronized(new StreamReader(s, enc, false, _DefaultConsoleBufferSize)); } System.Threading.Thread.MemoryBarrier(); _in = tr; } } } return _in; } } public static TextWriter Out { get { // Hopefully this is inlineable. if (_out == null) InitializeStdOutError(true); return _out; } } private static void InitializeStdOutError(bool stdout) { // Set up Console.Out or Console.Error. lock (InternalSyncObject) { if (stdout && _out != null) return; else if (!stdout && _error != null) return; TextWriter writer = null; Stream s; if (stdout) s = OpenStandardOutput(_DefaultConsoleBufferSize); else s = OpenStandardError(_DefaultConsoleBufferSize); if (s == Stream.Null) { #if _DEBUG if (CheckOutputDebug()) writer = MakeDebugOutputTextWriter((stdout) ? "Console.Out: " : "Console.Error: "); else #endif // _DEBUG writer = TextWriter.Synchronized(StreamWriter.Null); } else { int codePage = (int)GetConsoleOutputCP(); Encoding encoding = Encoding.GetEncoding(codePage); StreamWriter stdxxx = new StreamWriter(s, encoding, _DefaultConsoleBufferSize); //stdxxx.HaveWrittenPreamble = true; stdxxx.AutoFlush = true; writer = TextWriter.Synchronized(stdxxx); } if (stdout) _out = writer; else _error = writer; } } // This method is only exposed via methods to get at the console. // We won't use any security checks here. private static Stream GetStandardFile(int stdHandleName, FileAccess access, int bufferSize) { // We shouldn't close the handle for stdout, etc, or we'll break // unmanaged code in the process that will print to console. // We should have a better way of marking this on SafeHandle. IntPtr handle = GetStdHandle(stdHandleName); SafeFileHandle sh = new SafeFileHandle(handle, false); // If someone launches a managed process via CreateProcess, stdout // stderr, & stdin could independently be set to INVALID_HANDLE_VALUE. // Additionally they might use 0 as an invalid handle. if (sh.IsInvalid) { // Minor perf optimization - get it out of the finalizer queue. sh.SetHandleAsInvalid(); return Stream.Null; } //BCLDebug.ConsoleError("Console::GetStandardFile for std handle "+stdHandleName+" succeeded, returning handle number "+handle.ToString()); Stream console = new MyConsoleStream(sh, access); // Do not buffer console streams, or we can get into situations where // we end up blocking waiting for you to hit enter twice. It was // redundant. return console; } public static Stream OpenStandardError() { return OpenStandardError(_DefaultConsoleBufferSize); } public static Stream OpenStandardError(int bufferSize) { if (bufferSize < 0) throw new ArgumentOutOfRangeException("bufferSize", "ArgumentOutOfRange_NeedNonNegNum"); return GetStandardFile(STD_ERROR_HANDLE, FileAccess.Write, bufferSize); } public static Stream OpenStandardInput() { return OpenStandardInput(_DefaultConsoleBufferSize); } public static Stream OpenStandardInput(int bufferSize) { if (bufferSize < 0) throw new ArgumentOutOfRangeException("bufferSize", "ArgumentOutOfRange_NeedNonNegNum"); return GetStandardFile(STD_INPUT_HANDLE, FileAccess.Read, bufferSize); } public static Stream OpenStandardOutput() { return OpenStandardOutput(_DefaultConsoleBufferSize); } public static Stream OpenStandardOutput(int bufferSize) { if (bufferSize < 0) throw new ArgumentOutOfRangeException("bufferSize", "ArgumentOutOfRange_NeedNonNegNum"); return GetStandardFile(STD_OUTPUT_HANDLE, FileAccess.Write, bufferSize); } } }

We appreciate your feedback.  If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.