Redirecting cmd.exe's standard I/O

Started by
4 comments, last by luke2 16 years, 3 months ago
Hello all, I've been trying to redirect the I/O of cmd.exe so that my app can send cmd input, and take cmd's output. My main resource for this has been this msdn article http://msdn2.microsoft.com/en-us/library/ms682499.aspx Creating a Child Process with Redirected Input and Output (Windows) I've been focusing on using anonymous pipes, which seem to be suited to my needs, but there is a problem... It works alright the first time I input something, I get the console's opening screen (Microsoft Windows...Copyright...current_directory>[cursor here]). When I type in any command, dir for example, I then get a newline with just "More?" and no output from the previous command. After the "More?" line, I don't get any more output from the pipe. As best I can tell, the code in the prior article, which mine is based off of, is meant to only make one read/write pass with the child process- as opposed to doing all the reading/writing that I need to. The suspect code segments are my Read and Write pipe functions: ReadFromPipe

DWORD ReadFromPipe( char* buff, DWORD readLength ) 
{ 
   DWORD dwRead; 
 
   if ( hChildStdoutWr != NULL ) 
   {
	   CloseHandle(hChildStdoutWr);
	   hChildStdoutWr = NULL;
   }
 
// Read output from the child process, and write to parent's STDOUT. 
    memset( buff, -1, BUFSIZE );
	DWORD x = ReadFile( hChildStdoutRd, buff, readLength, &dwRead, NULL);
	return x;
} 


WriteToPipe

DWORD WriteToPipe( LPCVOID data, DWORD length ) 
{ 
	DWORD writtenBytes;

	BOOL b = WriteFile(hChildStdinWr, data, length, &writtenBytes, NULL);

	if ( hChildStdinWr != NULL ) 
	{
	   CloseHandle(hChildStdinWr);
	   hChildStdinWr = NULL;
	}

	return writtenBytes;
} 


Now, you see those if statements, which close my StdinWr and StdoutWr handles? I don't think I should have those there, but without them, I don't even get the "More?" line... Here is the main loop of my program:

//Inside of void ClientProc( const char* ip )

while( true ) //ClientDisConn should end this program.
{
	PollEvents( host );
	PeekNamedPipe( hChildStdoutRd, data, BUFSIZE, &bytesRead, &availBytes, NULL );
	if( bytesRead > 0 )
	{
		ReadFromPipe( data, (DWORD)BUFSIZE );
		int length = 0;
		for( int i = 0; i < BUFSIZE; i ++ )
			if( data > -1 )
				length++;
			else
				break;
		printf( "Message sent\n" );
		PACKET* p = CreatePacket( data , (UINT)length );
		SendPacket( p, peer );
	}
}


And incase the problem isn't in there, here is my entire program:

#include "ServerAndClientProcs.h"
#include "NetWrapper.h"
#include "enet.h"
#include &lt;windows.h&gt;
#include &lt;stdio.h&gt;
#include &lt;conio.h&gt;
#include &lt;iostream&gt;
#include &lt;list&gt;


HANDLE hChildStdinRd, hChildStdinWr,  
   hChildStdoutRd, hChildStdoutWr, 
   hInputFile, hStdout;

std::list&lt;PEER*&gt; peerList; 

bool CreateControlledCMD();
bool CreateChildProcess();
DWORD WriteToPipe( LPCVOID data, DWORD length );
DWORD ReadFromPipe( char* buff, DWORD readLength );

void ServerConn( EVENT &e );
void ServerMsgRecv( EVENT &e );
void ServerDisConn( EVENT &e );

void ClientConn( EVENT &e );
void ClientMsgRecv( EVENT &e );
void ClientDisConn( EVENT &e );

void ServerProc()
{
	ADDRESS addr; 
		HOST host; 

		addr.host = ENET_HOST_ANY;
		addr.port = 1029; 
		host = *CreateHost( addr, 2 );

		NetEvent nConn = ServerConn;
		NetEvent nMsg = ServerMsgRecv;
		NetEvent nDisConn = ServerDisConn;

		RegisterCallBacks( &nConn, &nMsg, &nDisConn );
		
		std::string input;
		std::string terminate = "END";
		while( input != terminate )
		{
			PollEvents( &host );
			
			if( _kbhit() ) //Asynchronous console I/O -- almost an oxymoron..
			{
				char in = _getch();
				std::cout &lt;&lt; in;
				if( in == 0xD ) //0xD == ENTER
				{
					std::cout &lt;&lt; std::endl;
					PACKET* p;
					p = CreatePacket( (char*)input.c_str() , (UINT)input.length() + 1 );
					printf( "Packets sent\n" );
					for( std::list&lt;PEER*&gt;::iterator itor = peerList.begin(); itor != peerList.end(); ++itor )
						SendPacket( p, (*itor), 0 );
				}
				else
					input += in;
			}
		}
}

void ServerConn( ENetEvent &e )
{
	printf( "Client connected\n" );
	peerList.push_back( e.peer );
}

void ServerMsgRecv( ENetEvent &e )
{
	for( unsigned int i = 0; i &lt; e.packet-&gt;dataLength; i++ )
		std::cout &lt;&lt; e.packet-&gt;data;
}

void ServerDisConn( ENetEvent &e )
{
	printf( "Client disconnected\n" );
	peerList.remove( e.peer );
}

void ClientProc( const char* ip )
{
	ADDRESS addr; 
		
		char data[BUFSIZE];
		DWORD bytesRead, availBytes;
		RtlZeroMemory( data, BUFSIZE );

		HOST* host; 
		PEER* peer;

		addr.port = 1029; 
		host = CreateClient( );
		peer = Connect( host, addr, ip );
		if( peer != NULL )
			CreateControlledCMD();

		NetEvent nConn = ClientConn;
		NetEvent nMsg = ClientMsgRecv;
		NetEvent nDisConn = ClientDisConn;

		RegisterCallBacks( &nConn, &nMsg, &nDisConn );

		while( true ) //ClientDisConn should end this program.
		{
			PollEvents( host );
			PeekNamedPipe( hChildStdoutRd, data, BUFSIZE, &bytesRead, &availBytes, NULL );
			if( bytesRead &gt; 0 )
			{
				ReadFromPipe( data, (DWORD)BUFSIZE );
				int length = 0;
				for( int i = 0; i &lt; BUFSIZE; i ++ )
					if( data &gt; -1 )
						length++;
					else
						break;
				printf( "Message sent\n" );
				PACKET* p = CreatePacket( data , (UINT)length );
				SendPacket( p, peer );
			}
		}

}

void ClientConn( EVENT &e )
{
}

void ClientMsgRecv( ENetEvent &e )
{
	printf( "Message received\n" );
	WriteToPipe( (LPCVOID)e.packet-&gt;data, (DWORD)e.packet-&gt;dataLength );
}

void ClientDisConn( ENetEvent &e )
{
	ShutdownNet();
	ExitProcess(1);
}

 
bool CreateControlledCMD()
{ 
   SECURITY_ATTRIBUTES saAttr; 
   BOOL fSuccess; 
 
// Set the bInheritHandle flag so pipe handles are inherited. 
 
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
   saAttr.bInheritHandle = TRUE; 
   saAttr.lpSecurityDescriptor = NULL; 

// Get the handle to the current STDOUT. 
 
   hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
 
// Create a pipe for the child process's STDOUT. 
 
   if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) 
      return false; 

// Ensure that the read handle to the child process's pipe for STDOUT is not inherited.

   SetHandleInformation( hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);

// Create a pipe for the child process's STDIN. 
 
   if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) 
      return false; 

// Ensure that the write handle to the child process's pipe for STDIN is not inherited. 
 
   SetHandleInformation( hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
 
// Now create the child process. 
   
   fSuccess = CreateChildProcess();
   if (! fSuccess) 
      return false; 
 
   return true; 
} 
 
bool CreateChildProcess() 
{ 
   TCHAR szCmdline[]=TEXT("cmd.exe");
   PROCESS_INFORMATION piProcInfo; 
   STARTUPINFO siStartInfo;
   bool bFuncRetn = FALSE; 
 
// Set up members of the PROCESS_INFORMATION structure. 
 
   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
 
// Set up members of the STARTUPINFO structure. 
 
   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO); 
   siStartInfo.hStdError = hChildStdoutWr;
   siStartInfo.hStdOutput = hChildStdoutWr;
   siStartInfo.hStdInput = hChildStdinRd;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
 
// Create the child process. 
    
   bFuncRetn = (bool)CreateProcess(NULL, 
      szCmdline,     // command line 
      NULL,          // process security attributes 
      NULL,          // primary thread security attributes 
      TRUE,          // handles are inherited 
      0,             // creation flags 
      NULL,          // use parent's environment 
      NULL,          // use parent's current directory 
      &siStartInfo,  // STARTUPINFO pointer 
      &piProcInfo);  // receives PROCESS_INFORMATION 
   
   if (bFuncRetn == 0) 
      return false;
   else 
   {
      CloseHandle(piProcInfo.hProcess);
      CloseHandle(piProcInfo.hThread);
      return bFuncRetn;
   }
}
 
DWORD WriteToPipe( LPCVOID data, DWORD length ) 
{ 
	DWORD writtenBytes;

	BOOL b = WriteFile(hChildStdinWr, data, length, &writtenBytes, NULL);

	if ( hChildStdinWr != NULL ) 
	{
	   CloseHandle(hChildStdinWr);
	   hChildStdinWr = NULL;
	}

	return writtenBytes;
} 
 
DWORD ReadFromPipe( char* buff, DWORD readLength ) 
{ 
   DWORD dwRead; 
 
   if ( hChildStdoutWr != NULL ) 
   {
	   CloseHandle(hChildStdoutWr);
	   hChildStdoutWr = NULL;
   }
 
// Read output from the child process, and write to parent's STDOUT. 
    memset( buff, -1, BUFSIZE );
	DWORD x = ReadFile( hChildStdoutRd, buff, readLength, &dwRead, NULL);
	return x;
} 
 
VOID ErrorExit (LPSTR lpszMessage) 
{ 
   fprintf(stderr, "%s\n", lpszMessage); 
   ExitProcess(0); 
}

Btw, this program is actually a networked one, eg I have a server, which the client connect too, and the server acts like a remote command line.
Advertisement
Quote:Original post by luke2
Btw, this program is actually a networked one, eg I have a server, which the client connect too, and the server acts like a remote command line.


Use: _execl, _wexecl to execute "commands".

You should note that when one executes a command on the command line they are simply executing an application, usually with arguments.
Although that might be enough to fib a cmd console to an 'average' user, I don't think that will be enough, especially if the user tries to do some of the more complex command line stuff that aren't actually programs.

Not only that, but the method you've described would not allow interactive applications - only ones that take input as a parameter.

And that is essentially my program, a remote, interactive command line.
(Please note that I am having NO issues with the remote part, only the interactive part of my program isn't working...)
Surely my programing has not surpassed all others' in complexity?
Quote:Surely my programing has not surpassed all others' in complexity?

Don't think so ;) but this isn't the best forum for these kind of questions.
A forum aimed at windows system programming would be more suitable.

Haven't had the time to read through your code and I've never written a program that sends input to a console app before.

I have however written a program that captures console outputs and displays it in my own window.
I just dump the relevant code here (will most likely not compile).

DWORDConsoleWindow::executeThread(){// Set the bInheritHandle flag so pipe handles are inherited. 	SECURITY_ATTRIBUTES saAttr; 	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 	saAttr.bInheritHandle = TRUE; 	saAttr.lpSecurityDescriptor = NULL; 	HANDLE hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE); 	HANDLE hSaveStderr = GetStdHandle(STD_ERROR_HANDLE);  // Create a pipe for the child process's STDOUT. 	HANDLE hChildStdoutRd, hChildStdoutWr;	if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 32)){		errorSet("Stdout pipe creation failed");		return ~uint(0);	} // Set a write handle to the pipe to be STDOUT. 	if (!SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)){		errorSet("Stdout pipe creation failed");		CloseHandle(hChildStdoutRd);		CloseHandle(hChildStdoutWr);		return ~uint(0);	} // Set a write handle to the pipe to be STDERR.	if (!SetStdHandle(STD_ERROR_HANDLE, hChildStdoutWr)){		errorSet("Stderr pipe creation failed");		SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout);		CloseHandle(hChildStdoutRd);		CloseHandle(hChildStdoutWr);		return ~uint(0);	}// Create noninheritable read handle and close the inheritable read handle. 	HANDLE hChildStdoutRdDup;	bool success = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd, GetCurrentProcess(), &hChildStdoutRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS) == TRUE;	if (!success){		errorSet("DuplicateHandle failed");		CloseHandle(hChildStdoutRd);		CloseHandle(hChildStdoutWr);		SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout);		SetStdHandle(STD_ERROR_HANDLE, hSaveStderr);		return ~uint(0);	}	CloseHandle(hChildStdoutRd);//	Now create the child process. 	PROCESS_INFORMATION piProcInfo; 	STARTUPINFO siStartInfo; // Set up members of the PROCESS_INFORMATION structure. 	Mem::set(&piProcInfo, 0, sizeof(PROCESS_INFORMATION));// Set up members of the STARTUPINFO structure. 	Mem::set(&siStartInfo, 0, sizeof(STARTUPINFO));	siStartInfo.cb = sizeof(STARTUPINFO);//	siStartInfo.dwFlags = STARTF_USESHOWWINDOW;//	siStartInfo.wShowWindow = SW_HIDE;// Create the child process. 	char command[8192];	strncpy(command, (std::string("cmd.exe /C ") + m_command).c_str(), 8192);	if (!CreateProcess(NULL, command, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &siStartInfo, &piProcInfo)){		errorSet("Create process failed");		CloseHandle(hChildStdoutRdDup);		CloseHandle(hChildStdoutWr);		SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout);		SetStdHandle(STD_ERROR_HANDLE, hSaveStderr);		return ~uint(0);	}	m_hChildProcess = piProcInfo.hProcess;	m_dwProcessGroupId = piProcInfo.dwProcessId;//	After process creation, restore the saved STDOUT. 	if (!SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)){		errorSet("Re-redirecting Stdout failed");		SetStdHandle(STD_ERROR_HANDLE, hSaveStderr);		CloseHandle(hChildStdoutRdDup);		CloseHandle(hChildStdoutWr);		return ~uint(0);	}//	After process creation, restore the saved STDERR. 	if (!SetStdHandle(STD_ERROR_HANDLE, hSaveStderr)){		errorSet("Re-redirecting Stdout failed");		CloseHandle(hChildStdoutRdDup);		CloseHandle(hChildStdoutWr);		return ~uint(0);	}// Close the write end of the pipe before reading from the read end of the pipe. 	if (!CloseHandle(hChildStdoutWr)){		errorSet("CloseHandle");		CloseHandle(hChildStdoutRdDup);		return ~uint(0);	}// Read from pipe that is the standard output for child process. 	std::string log;	uint logPos = 0;	bool logAppend = false;	setColor(0x000000);	char temp[256 + 1];	for (; ; ){		DWORD read;		if ((!ReadFile(hChildStdoutRdDup, temp, sizeof(temp), &read, NULL)) || (read == 0))			break;		temp[read] = 0;		OemToChar(temp, temp);		{			uint i;			for (i = 0; i < read; ++ i)				print(temp);		}		m_redraw = true;	}	if ((m_logName.length() > 0) && (log.length() > 0)){		OutStream* o = FileOutStream::open(m_logName, true, 3.0f, logAppend);		if (!o){			Error::userHandle(true);		}else {			o->write(&log[0], log.length());			o->close();		}	}	CloseHandle(hChildStdoutRdDup);	DWORD code;	if (!GetExitCodeProcess(piProcInfo.hProcess, &code))		code = ~uint(0);	if(m_hChildProcess) {		CloseHandle(m_hChildProcess); // leave it running though (if not already ended)		m_hChildProcess = NULL;	}	setColor(0xff0000);	print("\nApplication returned: %d\n", code);	return code;}


Also check out: Real-Time Console Output Redirection
Thanks eq, but I've already figured out the problem:
It was all in the way that I formated my data, if I send just 'dir' to the console, it waits for the enter key before doing anything. All I needed to do was add '\n' to simulate an enter key. After that, just a few server side bugs, and I have a working example!

If any1 would like to see the working copy of my program, just ask and I can send it to you ;)

And btw, eq, you mentioned sites about windows system programming; would you mind giving me some linkys to a few?

Thanks for your help all!

This topic is closed to new replies.

Advertisement