-
Encapsulating cmd.exe
I'm trying to encapsulate cmd.exe in a C# program by spawning the process and redirecting StdIn and StdOut to different controls.
Basically, I have a text box for output and text box for input. When the program starts, I spawn the process, and then start two threads, for StdOut and StdError.
The StdOut thread loops indefinitely, appending line at a time to the output text control. If the stream ends, the thread exits. The StdError thread just reads in anything in the stream, because I heard that the process stalls if something is sitting in StdError.
When some text is entered into the input text control, I send that input to the StdIn stream.
The results are discouraging. When the process is spawned, nothing is written to the text control. Nothing happens when I write to the input control. However, task manager shows that cmd.exe has been spawned.
I managed to get partial results using a couple non-thread methods previously. One involved using a console project, so I could just have a loop running indefinitely on the main thread for the StdOut stream, and the console provided the StdIn from the keyboard, using the cmd.exe process. This worked ok, but that was without redirecting the StdIn. I didn't want the console hanging around, so I moved to just windows forms.
I tried a similar method with the forms, redirecting StdOut by running a loop on the main thread, to write each line to a text control. I didn't redirect StdIn. Unfortunately, the program only wrote the output of cmd.exe up until input would have been required, then cmd.exe was terminated. I believe this was because there was no console around to provide StdIn, so the spawned process had no choice but to terminate.
Now with the threads, nothing happens. Any advice on how I could resolve this, or go about it better?
Code:
public void Spawn()
{
//Start the process
m_proc = new Process();
m_proc.StartInfo.FileName = "cmd.exe";
m_proc.StartInfo.UseShellExecute = false;
m_proc.StartInfo.RedirectStandardOutput = true;
m_proc.StartInfo.RedirectStandardError=true;
m_proc.StartInfo.RedirectStandardInput=true;
m_proc.StartInfo.CreateNoWindow=true;
m_proc.Start();
//Threading
StdOutUseThreads();
StdErrUseThreads();
}
//Threading for stdout, a similar thread is created for stderr
public void StdOutUseThreads()
{
Thread ReaderThread=new Thread( new ThreadStart (StdOutReader) );
ReaderThread.Start();
}
public void StdOutReader()
{
for (;;)
{
string output=m_proc.StandardOutput.ReadLine();
if (null==output)
break;
m_text.AppendText(output+"\r\n");
}
}
//Runs on main thread, writes a line to StdIn
private void m_input_Leave(object sender, EventArgs e)
{
if (m_input.Text.Length>0)
{
m_proc.StandardInput.WriteLine(m_input.Text);
}
}
-
Does anyone have any advice?
-
I'm afraid I'm too good at C++, but here's how I did it in C in a text-based environment, if that helps at all.
Code:
#include <windows.h>
#include <stdio.h>
#define MAX_BUFFER_SIZE 512
int EmulateCommandPrompt()
{
STARTUPINFO sti = { 0 };
SECURITY_ATTRIBUTES sats = { 0 };
PROCESS_INFORMATION pi = { 0 };
HANDLE pipin_w, pipin_r, pipout_w, pipout_r;
BYTE buffer[MAX_BUFFER_SIZE];
DWORD writ, excode, read, available;
int ret = 0;
pipin_w = pipin_r = pipout_w = pipout_r = NULL;
for(;;)
{
//set SECURITY_ATTRIBUTES struct fields
sats.nLength = sizeof(sats);
sats.bInheritHandle = TRUE;
sats.lpSecurityDescriptor = NULL;
//create child's stdout pipes
if(!CreatePipe(&pipout_r, &pipout_w, &sats, 0)) break;
//and its stdin pipes
if(!CreatePipe(&pipin_r, &pipin_w, &sats, 0)) break;
//printf("Created pipes\n");
//now set STARTUPINFO struct fields (from the child's point of view)
sti.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
sti.wShowWindow = SW_HIDE;
sti.hStdInput = pipin_r;
sti.hStdOutput = pipout_w;
sti.hStdError = pipout_w;
//create the process...
if(!CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE,
0, NULL, NULL, &sti, &pi)) break;
//printf("Created process (%s)\n", cmdline);
//now have a continuous loop to get and recieve info
for(;;)
{
//make sure process is still running
GetExitCodeProcess(pi.hProcess, &excode);
if(excode != STILL_ACTIVE) break;
//printf("Process still running\n");
//give it time to set up/react
Sleep(500);
//now check to see if process has anything to say
if(!PeekNamedPipe(pipout_r, buffer,
sizeof(buffer), &read, &available, NULL)) ret = 10;
//printf("Peeked\n");
//is there anything to be read in the pipe?
if(read)
{
do
{
ZeroMemory(buffer, sizeof(buffer));
//read it and print to stdout
if(!ReadFile(pipout_r, buffer, sizeof
(buffer), &read, NULL) || !read) ret = 7;
buffer[read] = 0;
fprintf(stdout, "%s", buffer);
if(ret) break;
}
while(read >= sizeof(buffer));
}
//make sure we didn't run into any errors
if(!ret)
{
//get info and write it to pipe
ZeroMemory(buffer, sizeof(buffer));
fgets(buffer, sizeof(buffer), stdin);
if(!strnicmp(buffer, "exit", 4)) ret = 12;
if(!WriteFile(pipin_w, buffer, strlen(buffer),
&writ, NULL)) ret = 8;
}
if(ret) break;
}
break;
}
//clean up any unfinished business
if(pipin_w != NULL) CloseHandle(pipin_w);
if(pipin_r != NULL) CloseHandle(pipin_r);
if(pipout_w != NULL) CloseHandle(pipout_w);
if(pipout_r != NULL) CloseHandle(pipout_r);
if(pi.hProcess != NULL) CloseHandle(pi.hProcess);
if(pi.hThread != NULL) CloseHandle(pi.hThread);
return ret;
}
int main(int argc, char *argv[])
{
EmulateCommandPrompt();
return 0;
}
-
Thanks for the code but I think my primary problem is that I am trying to encapsulate a console based process without a console running in my process. This causes issues which I don't exactly understand. I'll try to incorporate your code though.
If anyone has any ideas please let me know.
-
Problem solved. Instead of reading a line at a time from Standard Output, read a character at a time.