Reputation: 1435
For a game server application I already have a console where some runtime information is displayed. Still, I would like to have another one where the admin can input commands (e.g. in case of emergency) while the resulting effects of these inputs is still displayed in the main console window.
There already are similar questions on stackoverflow concerning this topic, however, applying the answers didn't result in what I was hoping for. I have troubles understanding why on the one hand, I seemingly have to set UseShellExecute = true;
to actually get a new window while this make it impossible to RedirectStandardInput = true;
and vice-versa. However, having input and output via a new process but in the same console prompt works fine, except for the visual clutter (while you write, output is appended to your wirtten but not sent input which is quite uncomfortable).
So, is it still possible to use a separate command prompt for admin input (I guess so), or do I have to set up another form of inter-process communication and create a separate program (with Main-function and all)?
Here is my current code concerning the process generation. Note, that it is embedded in a less streamlined context if you should be wondering about the overall composition:
bool canExecute = false;
Process consoleProcess;
ProcessStartInfo startInfo = new ProcessStartInfo();
OperatingSystem os = Environment.OSVersion;
switch (os.Platform)
{
case PlatformID.MacOSX:
canExecute = false;
break;
case PlatformID.Unix:
canExecute = true;
startInfo.FileName = "/bin/bash";
break;
case PlatformID.Win32NT:
canExecute = true;
startInfo.FileName = "cmd.exe";
break;
case PlatformID.Win32S:
canExecute = true;
startInfo.FileName = "cmd.exe";
break;
case PlatformID.Win32Windows:
canExecute = true;
startInfo.FileName = "cmd.exe";
break;
case PlatformID.WinCE:
canExecute = true;
startInfo.FileName = "cmd.exe";
break;
case PlatformID.Xbox:
canExecute = false;
break;
}
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
consoleProcess = new Process();
consoleProcess.StartInfo = startInfo;
consoleProcess.Start();
if (canExecute)
{
using (StreamWriter sw = consoleProcess.StandardInput)
{
String line;
while ((line = Console.ReadLine()) != null)
{
// do something useful with the user input
}
}
}
Thank you in advance!
Upvotes: 4
Views: 1917
Reputation: 70671
You can't do this with just the built-in .NET Process
class. It doesn't support the right options.
The issue is that, by default, a new Windows console process always inherits the already-allocated console of its parent process. When you use UseShellExecute = true;
(the default for Process
), this causes the Process
class to (of course) use the ShellExecuteEx()
method. Since the new process is created through Windows Shell instead of your process, there's no console to inherit and so the process gets its own. But if you create the process directly, you get the default console-inheritance behavior.
But of course, since you want to redirect standard I/O, you can't use UseShellExecute = true;
. You have to set it to false
.
The only way around this is to call CreateProcess()
yourself directly, via p/invoke, so that you can pass the flag you need and which the Process
class doesn't offer a way to control. The flag in question is CREATE_NEW_CONSOLE
. Passed to the function call, it tells the CreateProcess()
function to create a separate console for the new process.
Please see MSDN's Creation of a Console for more information on this behavior and how to adjust it to suit your needs.
Naturally, this opens a whole new can of worms, as you will no longer have the direct convenience from the Process
class to aid with redirection of I/O. You might find it easier in the long run to just write a thin non-console proxy program to run the actual program. That way, you can start the non-console proxy, which of course will not inherit your current console, and then have it start the program you actually want to run. Doing it that way is not terribly elegant (not the least of which because, the proxy not being a console program, you won't be able to easily redirect I/O via stdio), but it's reasonably simple and keeps you in the managed-code world, with easier-to-use APIs.
Please find below an example of a bidirectional proxy (compiled as a "Windows Application", not a "Console Application"), with a parent process and a child process. The child process simply echoes to the console whatever is typed in. The parent process writes to the proxy whatever is typed in. The proxy sends to the child whatever it receives from the parent, and sends to the parent whatever it receives from the child. All processes treat an empty line input as the termination condition.
For your own purposes, you would likely use a one-way pipe (i.e. PipeDirection.In
for the proxy and PipeDirection.Out
for the parent process), and have the proxy redirect only StandardInput
. That way, all output will still appear in the child process's window. (The bidirectional example is more for proof-of-concept...obviously if both input and output are directed, there's not much point in forcing the child process into its own window :) ).
Proxy: (ConsoleProxy.exe)
class Program
{
static void Main(string[] args)
{
NamedPipeClientStream pipe = new NamedPipeClientStream(".", args[1],
PipeDirection.InOut, PipeOptions.Asynchronous);
pipe.Connect();
Process process = new Process();
process.StartInfo.FileName = args[0];
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
using (TextReader reader = new StreamReader(pipe))
using (TextWriter writer = new StreamWriter(pipe))
{
Task readerTask = ConsumeReader(process.StandardOutput, writer);
string line;
do
{
line = reader.ReadLine();
if (line != "")
{
line = "proxied write: " + line;
}
process.StandardInput.WriteLine(line);
process.StandardInput.Flush();
} while (line != "");
readerTask.Wait();
}
}
static async Task ConsumeReader(TextReader reader, TextWriter writer)
{
char[] rgch = new char[1024];
int cch;
while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
{
writer.Write("proxied read: ");
writer.Write(rgch, 0, cch);
writer.Flush();
}
}
}
Child process: (ConsoleApplication1.exe)
class Program
{
static void Main(string[] args)
{
Console.Title = "ConsoleApplication1";
string line;
while ((line = PromptLine("Enter text: ")) != "")
{
Console.WriteLine(" Text entered: \"" + line + "\"");
}
}
static string PromptLine(string prompt)
{
Console.Write(prompt);
return Console.ReadLine();
}
}
Parent process:
class Program
{
static void Main(string[] args)
{
NamedPipeServerStream pipe = new NamedPipeServerStream("ConsoleProxyPipe",
PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Console.Title = "Main Process";
Process process = new Process();
process.StartInfo.FileName = "ConsoleProxy.exe";
process.StartInfo.Arguments = "ConsoleApplication1.exe ConsoleProxyPipe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.Start();
pipe.WaitForConnection();
using (TextReader reader = new StreamReader(pipe))
using (TextWriter writer = new StreamWriter(pipe))
{
Task readerTask = ConsumeReader(reader);
string line;
do
{
line = Console.ReadLine();
writer.WriteLine(line);
writer.Flush();
} while (line != "");
readerTask.Wait();
}
}
static async Task ConsumeReader(TextReader reader)
{
char[] rgch = new char[1024];
int cch;
while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
{
Console.Write(rgch, 0, cch);
}
}
}
Upvotes: 1