Thread: question on CreateProcess() redirection

  1. #1
    Registered User
    Join Date
    May 2006
    Posts
    89

    question on CreateProcess() redirection

    Hi, I am trying to redirect input/output from a program called from CreateProcess(). I have found a lot of info on this but it all refered to coding the child process as well as the parent and in this case the child process is not written by me.

    I am trying to capture input from a command line telnet session using a command line version of putty. So far I have successfully launched the command line putty program and logged into the session but that was all handled with CreateProcess(). Now I want to start outputting the session to a text box and also be able to send the session commands via buttons. Is there anyway to grab the screen ouput from the created process and redirect it to my parent process to display it? Same with taking input from the parent and passing it to the child. i.e. Is there anyway to "fake" a keyboard input to the called process?

    thanks

  2. #2
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    Have you tried the MSDN code. It doesn't look to be dependent on code in the child (although a simple child program is provided as an example). The Putty documentation suggests using Plink.

  3. #3
    Registered User
    Join Date
    May 2006
    Posts
    89
    Okay I am using plink and have the program launching well enough. I went back and worked thru the MSDN code and tried to apply it to what I have but I can't really tell what it is doing besides not working. I setup my plink connections as a seperate class called pconnect. I believe my main issues stem from the fact the MSDN code uses files to read from and write to and I am trying to read from and write to the cout/cin of my parent program. However, with the usage of handles to the sockets I thought this was possible. Here is some code:

    main:
    Code:
    int main(int argc, char *argv[])
    {
        Pconnect connector;
        string path, param;
        char c;
    
        path  = "c:\\plink.exe";
        param = "-ssh *username*@*server*";
        int i = connector.ExecuteProcess(path, param, 0);
        Sleep(5000);
        connector.WriteToPipe("password\n");
        cout << "Wrote it!\n";
        connector.ReadFromPipe();
        cout << "Execute process finished with: " << i << endl;
        cin >> c; //dont close terminal window until characer is entered
    }
    Here is the pconnect header file. I am not sure if I should be defining the handles where I am but I didnt see why it should not work this way.

    Code:
    class Pconnect{
    	public:
    		
    		int ExecuteProcess(std::string &FullPathToExe, std::string &Parameters, int SecondsToWait);
    		void WriteToPipe(std::string s); 
    		char* ReadFromPipe();
         
    	private:
    		HANDLE hChildStdinRd, hChildStdinWr,  
    			hChildStdoutRd, hChildStdoutWr, 
    			hStdout;
    };
    Handle setup from what I gathered on the MSDN page: (this is happening inside ExecuteProcess)
    Code:
    //get handle of current stdout
    	hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    
    	//create a pipe for the child process's STDOUT
    	if(! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &as, 0))
    		std::cerr << "Stdout pipe creation failed.\n";
    
    	//Endure the read handle for STDOUT is not inherited.
    	SetHandleInformation( hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
    
    	//create pipe for childs STDIN
    	if(! CreatePipe(&hChildStdinRd, &hChildStdinWr, &as, 0))
    		std::cerr << "Stdin pipe creation failed.\n";
    
    	//Ensure write handle for STDIN is not inherited
    	SetHandleInformation( hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
    Write to pipe:
    Code:
    void Pconnect::WriteToPipe(string s) 
    { 
       DWORD dwWritten; 
       CHAR chBuf[BUFSIZE]; 
    
       strcpy(chBuf, s.c_str());
     
    // Read from a file and write its contents to a pipe. 
    
          if (! WriteFile(hChildStdinWr, chBuf, BUFSIZE, 
             &dwWritten, NULL))
    		 std::cerr << "Write to pipe failed.\n";
    // Close the pipe handle so the child process stops reading. 
     
       if (! CloseHandle(hChildStdinWr)) 
    	   std::cerr << "Close pipe failed.\n";
    }
    Read from pipe:
    Code:
    char* Pconnect::ReadFromPipe() 
    { 
       DWORD dwRead, dwWritten; 
       CHAR chBuf[BUFSIZE]; 
    
    // Close the write end of the pipe before reading from the 
    // read end of the pipe. 
     
       if (!CloseHandle(hChildStdoutWr)) 
         std::cerr << "Closing handle failed.\n";
     
    // Read output from the child process, and write to parent's STDOUT. 
     
       for (;;) 
       { 
         if( !
          ReadFile( hChildStdoutRd, chBuf, BUFSIZE, &dwRead, 
             NULL) || dwRead == 0) break;
          if (! WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL)) 
             break; 
       } 
       return chBuf;
    }

  4. #4
    Registered User
    Join Date
    May 2006
    Posts
    89
    anyone?

  5. #5
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    If you post the complete compilable code that you have tried, we can take a look. The code you have posted looks alright but it is only part of the code required. Also, "not working" is not much to go on. Where is it stopping? Is a function returning an error code?

  6. #6
    Registered User
    Join Date
    May 2006
    Posts
    89
    it compiles fine but it just doesnt seem to be sending anything to the child process or receiving anything. As you can see in my main I tried to send a password to the child process and then tried to receive the next thing the process sees which should be a welcome type screen for the server im connecting to. Here is the complete pconnect code, the header and the main are in the last post so that is everything.

    Code:
    #include "pconnect.h"
    
    using namespace std;
    
    
    int Pconnect::ExecuteProcess(string &FullPathToExe, string &Parameters, int SecondsToWait) 
    {
        int iMyCounter = 0, iReturnVal = 0;
        DWORD dwExitCode;
    
        /* - NOTE - You could check here to see if the exe even exists */
    
    	const char * szPath = FullPathToExe.c_str();
    	
    	char * pszParam = new char[Parameters.size() + 1];
    	
    	strcpy(pszParam, Parameters.c_str());
    
        /* CreateProcess API initialization */
        STARTUPINFO siStartupInfo;
        PROCESS_INFORMATION piProcessInfo;
        SECURITY_ATTRIBUTES as;
    	siStartupInfo.hStdError = hChildStdoutWr;
        siStartupInfo.hStdOutput = hChildStdoutWr;
        siStartupInfo.hStdInput = hChildStdinRd;
        memset(&siStartupInfo, 0, sizeof(siStartupInfo));
        memset(&piProcessInfo, 0, sizeof(piProcessInfo));
        memset(&as, 0, sizeof(as));
        as.lpSecurityDescriptor=NULL;
        as.bInheritHandle=true;
        as.nLength=sizeof(as);
     //  siStartupInfo.dwFlags = STARTF_USESHOWWINDOW  | STARTF_USESTDHANDLES;
      //  siStartupInfo.wShowWindow = SW_HIDE;
        siStartupInfo.cb = sizeof(siStartupInfo);
    /////////////////////////////////////////////////////////////////////////////////////////////
    
    
    	//get handle of current stdout
    	hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    
    	//create a pipe for the child process's STDOUT
    	if(! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &as, 0))
    		std::cerr << "Stdout pipe creation failed.\n";
    
    	//Endure the read handle for STDOUT is not inherited.
    	SetHandleInformation( hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
    
    	//create pipe for childs STDIN
    	if(! CreatePipe(&hChildStdinRd, &hChildStdinWr, &as, 0))
    		std::cerr << "Stdin pipe creation failed.\n";
    
    	//Ensure write handle for STDIN is not inherited
    	SetHandleInformation( hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
    
    	////MSDN creates child Process here//////
    
    
    ////////////////////////////////////////////////////////////////////////////////////////
        /* Execute */
    	if ((CreateProcess(szPath, pszParam, 0, 0, true,
                                CREATE_NEW_CONSOLE, 0, 0, &siStartupInfo,  //use create new console so i can see what the child proc is actually doing
                                &piProcessInfo)) != false)
        { 
           /* A loop to watch the process. Dismissed with SecondsToWait set to 0 */
           GetExitCodeProcess(piProcessInfo.hProcess, &dwExitCode);
    
           while (dwExitCode == STILL_ACTIVE && SecondsToWait != 0)
           {
               GetExitCodeProcess(piProcessInfo.hProcess, &dwExitCode);
               Sleep(500);
               iMyCounter += 500;
    
               if (iMyCounter > (SecondsToWait * 1000)) 
               { 
                   dwExitCode = 0;
               } 
           }
        }
        else
        {
            /* CreateProcess failed. You could also set the return to GetLastError() */
            iReturnVal = 2;
        }
    
        /* Release handles */
        CloseHandle(piProcessInfo.hProcess);
        CloseHandle(piProcessInfo.hThread);
    
        /* Free memory */
        delete[]pszParam;
    	delete[]szPath;
    
        return iReturnVal;
    }
    
    void Pconnect::WriteToPipe(string s) 
    { 
       DWORD dwWritten; 
       CHAR chBuf[BUFSIZE]; 
    
       strcpy(chBuf, s.c_str());
     
    // Read from a file and write its contents to a pipe. 
    
          if (! WriteFile(hChildStdinWr, chBuf, BUFSIZE, 
             &dwWritten, NULL))
    		 std::cerr << "Write to pipe failed.\n";
    // Close the pipe handle so the child process stops reading. 
     
       if (! CloseHandle(hChildStdinWr)) 
    	   std::cerr << "Close pipe failed.\n";
    } 
    
    char* Pconnect::ReadFromPipe() 
    { 
       DWORD dwRead, dwWritten; 
       CHAR chBuf[BUFSIZE]; 
    
    // Close the write end of the pipe before reading from the 
    // read end of the pipe. 
     
       if (!CloseHandle(hChildStdoutWr)) 
         std::cerr << "Closing handle failed.\n";
     
    // Read output from the child process, and write to parent's STDOUT. 
     
       for (;;) 
       { 
         if( !
          ReadFile( hChildStdoutRd, chBuf, BUFSIZE, &dwRead, 
             NULL) || dwRead == 0) break;
          if (! WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL)) 
             break; 
       } 
       return chBuf;
    }

  7. #7
    Registered Abuser
    Join Date
    Jun 2006
    Location
    Toronto
    Posts
    591
    Maybe try the "popen" function. It's a relatively basic method (in stdio.h) but I found the stream that it creates is easily readible from and writable to.

  8. #8
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    I second the suggestion of using _popen if your program is a console program.

    The problem with the current code is that you have changed the order of the code. This means you are using the pipe handles before they are created to fill in the STARTUPINFO structure.

    I have edited your code and posted it below, although I have not tested it:
    Code:
    #include "pconnect.h"
    
    using namespace std;
    
    
    int Pconnect::ExecuteProcess(string &FullPathToExe, string &Parameters, int SecondsToWait) 
    {
        int iMyCounter = 0, iReturnVal = 0;
        DWORD dwExitCode;
    
        /* - NOTE - You could check here to see if the exe even exists */
    
    	const char * szPath = FullPathToExe.c_str();
    	
    	char * pszParam = new char[Parameters.size() + 1];
    	
    	strcpy(pszParam, Parameters.c_str());
    
    	//get handle of current stdout
    	hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    
    	//create a pipe for the child process's STDOUT
    	if(! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &as, 0))
    		std::cerr << "Stdout pipe creation failed.\n";
    
    	//Endure the read handle for STDOUT is not inherited.
    	SetHandleInformation( hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
    
    	//create pipe for childs STDIN
    	if(! CreatePipe(&hChildStdinRd, &hChildStdinWr, &as, 0))
    		std::cerr << "Stdin pipe creation failed.\n";
    
    	//Ensure write handle for STDIN is not inherited
    	SetHandleInformation( hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
    
    
        /* CreateProcess API initialization */
        STARTUPINFO         siStartupInfo = { 0 };
        PROCESS_INFORMATION piProcessInfo = { 0 };
        SECURITY_ATTRIBUTES as            = { 0 };
    
        siStartupInfo.cb          = sizeof(siStartupInfo);
        siStartupInfo.hStdError   = hChildStdoutWr;
        siStartupInfo.hStdOutput  = hChildStdoutWr;
        siStartupInfo.hStdInput   = hChildStdinRd;
        siStartupInfo.dwFlags     = STARTF_USESHOWWINDOW  | STARTF_USESTDHANDLES;
        siStartupInfo.wShowWindow = SW_SHOW;
    
        as.nLength              = sizeof(as);
        as.lpSecurityDescriptor = NULL;
        as.bInheritHandle       = TRUE;
    
    ////////////////////////////////////////////////////////////////////////////////////////
        /* Execute */
    	if ((CreateProcess(szPath, pszParam, 0, 0, TRUE,
                                CREATE_NEW_CONSOLE, 0, 0, &siStartupInfo,  //use create new console so i can see what the child proc is actually doing
                                &piProcessInfo)) != false)
        { 
           // Wait until either the process has finished or SecondsToWait has expired...
           WaitForSingleObject(piProcessInfo.hProcess, SecondsToWait * 1000);
        }
        else
        {
            /* CreateProcess failed. You could also set the return to GetLastError() */
            iReturnVal = 2;
        }
    
        /* Release handles */
        CloseHandle(piProcessInfo.hProcess);
        CloseHandle(piProcessInfo.hThread);
    
        /* Free memory */
        delete[]pszParam;
    
        ///	delete[]szPath; // do not delete the return value of std::string::c_str()
    
        return iReturnVal;
    }
    
    void Pconnect::WriteToPipe(string s) 
    { 
       DWORD dwWritten; 
       CHAR chBuf[BUFSIZE]; 
    
       strcpy(chBuf, s.c_str());
     
    // Read from a file and write its contents to a pipe. 
    
          if (! WriteFile(hChildStdinWr, chBuf, BUFSIZE, 
             &dwWritten, NULL))
    		 std::cerr << "Write to pipe failed.\n";
    // Close the pipe handle so the child process stops reading. 
     
       if (! CloseHandle(hChildStdinWr)) 
    	   std::cerr << "Close pipe failed.\n";
    } 
    
    char* Pconnect::ReadFromPipe() 
    { 
       DWORD dwRead, dwWritten; 
       CHAR chBuf[BUFSIZE]; 
    
    // Close the write end of the pipe before reading from the 
    // read end of the pipe. 
     
       if (!CloseHandle(hChildStdoutWr)) 
         std::cerr << "Closing handle failed.\n";
     
    // Read output from the child process, and write to parent's STDOUT. 
     
       for (;;) 
       { 
         if( !
          ReadFile( hChildStdoutRd, chBuf, BUFSIZE, &dwRead, 
             NULL) || dwRead == 0) break;
          if (! WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL)) 
             break; 
       } 
       return chBuf;
    }

  9. #9
    Registered User
    Join Date
    May 2006
    Posts
    89
    well right now i am trying to get this to work as a console program but eventually I would like my program to be GUI based but still create a commandline child process which will be hidden. Will _popen() still work in this case? You mentioned using it "if my program is a console program" but I'm not sure if you were referring to my actual program or the one im trying to create. thanks
    Last edited by ac251404; 07-14-2006 at 08:53 AM.

  10. #10
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    >> Will _popen() still work in this case? <<

    No. See _popen documentation.

  11. #11
    Registered User
    Join Date
    May 2006
    Posts
    89
    Okay the code you edited almost worked but I had to move the security attributes stuff above the handle/pipe setup because the CreatePipe function takes a pointer to the security attributes struct. After things are looking much better, my plink window is blank and all of its normal output is being output to my screen.
    My new problem is this: ReadFile() which is used in my ReadFromPipe() functions is blocking. This is causing my program to just hang when it hits the first menu the server gives. You can enter a numerical choice 1-3 but I can't give a cin or WriteToPipe() because everything just hangs in ReadFromPipe(). I know non-blocking IO is a bit more difficult but is there any easy way I can test the stream to see if I have what I want so I can move on?

    edit: found this article which helped me figure out how to examine the stream so its now non-blocking... now I just need to figure out how to structure a loop to work with what I've got. I wasnt really sure if I'd be able to get my program this far, big thanks to you anonytmouse, would have been lost without your guidance. I'll probably still be running into a few issues tho so dont wander to far
    Last edited by ac251404; 07-14-2006 at 09:07 AM.

  12. #12
    Registered User
    Join Date
    May 2006
    Posts
    89
    okay everything has been humming along nicely and I decided to try and move my code into a GUI program. The problem I am having is that whenever I click the button I see the process is launched but it is being instantly closed again. Here is some code:

    Code:
    private: System::Void bConnect_Click(System::Object^  sender, System::EventArgs^  e) {				
    				 String ^un, ^pw, ^rd;
    				 std::string path, params, username, password, read;
    				 Pconnect connector;
    
    				 un = fUsername->Text;
    				 pw = fPassword->Text;
    				 path = "C:\\plink.exe";
    				 To_string(un, username);
    				 To_string(pw, password);
    				 params = " -ssh " + username + "@server.server.com -pw" + password;
    				 int r = connector.ExecuteProcess(path, params, 0);
    				 if (r != 0){
    						MessageBox::Show("Error executing process.", "OPTIM-App",
    						MessageBoxButtons::OK, MessageBoxIcon::Asterisk);
    				 }
    				 rd = gcnew String(connector.ReadFromPipe().c_str());
    					 rOutput->Text = rd;
    			 }
    Now I saw the obivous problem here in that I was initializing my Pconnect object inside my button so once it left the Button_Click function it was destroyed and the process was closed. The problem is, I cannot figure out how to declare a Pconnect object outside of the button function. I originally just stuck it there to test if it worked and forgot about it but now I am trying to move it so the process can exist for the life of my program but can't figure out the syntax. I am not using any special namespace besides std for my class so what would the syntax be to declare a Pconnect object, none of the obvious choices seem to work.
    Thanks

  13. #13
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    You appear to be mixing .NET and Win32 unnecessarily. If you are using .NET, you may as well avoid Win32 as much as possible. For example, in .NET, the console redirection can be done easily without Win32. C# seems to be more favoured as a .NET language, rather than C++.NET

    As for your question on variable lifetime, there are two options. The first is to declare the variable as a global. This is done by declaring the variable (Pconnect connector) outside any function, generally at the top of the file, below the includes. The other is to create the object on the heap (using new) and pass it around to all the functions that need to use it. In your case, I would suggest using the first method.

  14. #14
    Registered User
    Join Date
    May 2006
    Posts
    89
    I don't mean to mix .NET and Win32. I am CS student in college and my programming knowledge is completely limited to C++ and about half a year of Java. All the c++ we do is a Unix environment and focuses on algorithm design and analysis and working in the STL. This is just a project I am working on over the summer to make my job easier and try to learn something new. A lot of the code has been cannibalised from other places and I've re-written what I could where I needed in c++, thus the code you see. I guess I should really start looking at C# and learning it in a more structured way rather than in bits and pieces.

    At this point, mixing the code like I am is beginning to cause more issues than it is worth, and I believe it is only going to get worse the more my app grows. In your opinion, with my code as it is now, do you think it would be easier to go back and replace the Win32 things with .NET or vice-versa or re-write it from the ground up using one or the other?
    Thanks

    PS - Declaring it as global worked. thx

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Linux file redirection buffering issue !!!!
    By AirSeb in forum Linux Programming
    Replies: 8
    Last Post: 03-14-2009, 05:32 PM
  2. Alice....
    By Lurker in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 06-20-2005, 02:51 PM
  3. Question...
    By TechWins in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 07-28-2003, 09:47 PM
  4. redirection program help needed??
    By Unregistered in forum Linux Programming
    Replies: 0
    Last Post: 04-17-2002, 05:50 AM
  5. Question, question!
    By oskilian in forum A Brief History of Cprogramming.com
    Replies: 5
    Last Post: 12-24-2001, 01:47 AM