How to read output from cmd.exe using CreateProces

2019-02-17 14:59发布

How to read output from cmd.exe using CreateProcess() and CreatePipe()

I have been trying to create a child process executing cmd.exe with a command-line designating /K dir. The purpose is to read the output from the command back into the parent process using pipes.

I've already got CreateProcess() working, however the step involving pipes are causing me trouble. Using pipes, the new console window is not displaying (like it was before), and the parent process is stuck in the call to ReadFile().

Does anyone have an idea of what I'm doing wrong?

#include <Windows.h>
#include <stdio.h>
#include <tchar.h>

#define BUFFSZ 4096

HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;

int wmain(int argc, wchar_t* argv[]) 
{
    int result;
    wchar_t aCmd[BUFFSZ] = TEXT("/K dir"); // CMD /?
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    SECURITY_ATTRIBUTES sa;

    printf("Starting...\n");

    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));

    // Create one-way pipe for child process STDOUT
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) {
        printf("CreatePipe() error: %ld\n", GetLastError());
    }

    // Ensure read handle to pipe for STDOUT is not inherited
    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) {
        printf("SetHandleInformation() error: %ld\n", GetLastError());
    }

    // Create one-way pipe for child process STDIN
    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) {
        printf("CreatePipe() error: %ld\n", GetLastError());
    }

    // Ensure write handle to pipe for STDIN is not inherited
    if (!SetHandleInformation(g_hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, 0)) {
        printf("SetHandleInformation() error: %ld\n", GetLastError());
    }

    si.cb = sizeof(STARTUPINFO);
    si.hStdError = g_hChildStd_OUT_Wr;
    si.hStdOutput = g_hChildStd_OUT_Wr;
    si.hStdInput = g_hChildStd_IN_Rd;
    si.dwFlags |= STARTF_USESTDHANDLES;

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    // Pipe handles are inherited
    sa.bInheritHandle = true;

    // Creates a child process
    result = CreateProcess(
        TEXT("C:\\Windows\\System32\\cmd.exe"),     // Module
        aCmd,                                       // Command-line
        NULL,                                       // Process security attributes
        NULL,                                       // Primary thread security attributes
        true,                                       // Handles are inherited
        CREATE_NEW_CONSOLE,                         // Creation flags
        NULL,                                       // Environment (use parent)
        NULL,                                       // Current directory (use parent)
        &si,                                        // STARTUPINFO pointer
        &pi                                         // PROCESS_INFORMATION pointer
        );

    if (result) {
        printf("Child process has been created...\n");
    }
    else {
        printf("Child process could not be created\n");
    }

    bool bStatus;
    CHAR aBuf[BUFFSZ + 1];
    DWORD dwRead;
    DWORD dwWrite;
    // GetStdHandle(STD_OUTPUT_HANDLE)

    while (true) {
        bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
        if (!bStatus || dwRead == 0) {
            break;
        }
        aBuf[dwRead] = '\0';
        printf("%s\n", aBuf);
    }

        // Wait until child process exits
        WaitForSingleObject(pi.hProcess, INFINITE);

        // Close process and thread handles
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        printf("Stopping...\n");

        return 0;
    }

标签: c++ windows cmd
4条回答
成全新的幸福
2楼-- · 2019-02-17 15:14

Here is an example (taken from a larger program) of a thread that does what you are looking for. It creates pipes for stdout and stderr for the process it creates then goes into a loop reading those pipes until the program finishes.

DWORD WINAPI ThreadProc(LPVOID lpParameter)
   {
#define EVENT_NAME "Global\\RunnerEvt"

   HANDLE hev;
   SECURITY_ATTRIBUTES psa;
   InitSAPtr(&psa);
   DWORD waitRc;
   DWORD bytesRead;
   int manual_triggered = 1;

   hev = CreateEvent(&psa, FALSE, FALSE, EVENT_NAME);

   // Create pipes we'll read

      for(;;)
      {

      if (manual_triggered)
         {
         waitRc = WAIT_OBJECT_0;
         manual_triggered = 0;
         }
      else
         {
         waitRc = WaitForSingleObject(hev, 500);
         }

      if (waitRc == WAIT_OBJECT_0)
         {
         `logprint`f(LOG_DBG, "Received command to run process\n");

         CreateChildOutFile();

         stdOutEvt = CreateEvent(&psa, TRUE, FALSE, 0);
         stdOutOvl.hEvent = stdOutEvt;

         stdErrEvt = CreateEvent(&psa, TRUE, FALSE, 0);
         stdErrOvl.hEvent = stdErrEvt;

         gStdOutReadHand =  CreateNamedPipe(STD_OUT_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE,
            PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa);
         if (gStdOutReadHand == INVALID_HANDLE_VALUE)
            {
            log(LOG_DBG, "Error %d on create STDOUT pipe\n", GetLastError());
            }

         gStdErrReadHand =  CreateNamedPipe(STD_ERR_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE,
            PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa);
         if (gStdErrReadHand == INVALID_HANDLE_VALUE)
            {
            log(LOG_DBG, "Error %d on create STDERR pipe\n", GetLastError());
            }

         runProcess();

         log(LOG_DBG, "After runProcess, new PID is %d/%x\n", piProcInfo.dwProcessId, piProcInfo.dwProcessId);

         if (piProcInfo.dwProcessId == 0)
            {
            log(LOG_DBG, "runProcess failed, closing child STDIN/STDERR\n");
            closeChildPipes();

#define FAIL_MSG "Child process failed to start\n"
            writeChildOutFile(FAIL_MSG, strlen(FAIL_MSG) );

            CloseHandle(hChildOut);
            }
         else
            {
            log(LOG_DBG, "Child process created, setting up for redir/restart/termination\n");

            issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail);
            //log(LOG_DBG, "After read set on STDOUT\n");

            issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail);
            //log(LOG_DBG, "After read set on STDERR\n");

            HANDLE harr[4];

            for(;;)
               {
               harr[0] = hev;
               harr[1] = piProcInfo.hProcess;
               harr[2] = stdOutEvt;
               harr[3] = stdErrEvt;

               DWORD waitRc2 = WaitForMultipleObjects(4, harr, FALSE, 500);

               #if 0
               if (waitRc2 == -1)
                  {
                  log(LOG_DBG, "Wait error %d\n", GetLastError());
                  Sleep(500);
                  }

               log(LOG_DBG, "waitRc2 %d\n", waitRc2);
               #endif


               if ((waitRc2 - WAIT_OBJECT_0) == 0)
                  {
                  log(LOG_DBG, "Woke up because another trigger command was received\n");
                  #define NEW_CMD_MSG "Child process is being terminated because new trigger received\n"

                  writeChildOutFile(NEW_CMD_MSG, strlen(NEW_CMD_MSG));

                  terminateChild();
                  CloseHandle(hChildOut);
                  manual_triggered = 1;
                  break;
                  }
               else if ((waitRc2 - WAIT_OBJECT_0) == 1)
                  {
                  //log(LOG_DBG, "Woke up because child has terminated\n");
                  closeChildPipes();
                  #define NORM_MSG "Normal child process termination\n"
                  writeChildOutFile(NORM_MSG, strlen(NORM_MSG));
                  CloseHandle(hChildOut);
                  break;
                  }
               else if ((waitRc2 - WAIT_OBJECT_0) == 2)
                  {
                  //log(LOG_DBG, "Woke up because child has stdout\n");
                  if (GetOverlappedResult(gStdOutReadHand, &stdOutOvl, &bytesRead, TRUE))
                     {
                     writeChildOutFile(stdOutBuff, bytesRead);
                     ResetEvent(stdOutEvt);
                     issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail);
                     }

                  }
               else if ((waitRc2 - WAIT_OBJECT_0) == 3)
                  {
                  //log(LOG_DBG, "Woke up because child has stderr\n");

                  if (GetOverlappedResult(gStdErrReadHand, &stdErrOvl, &bytesRead, TRUE))
                     {
                     writeChildOutFile(stdErrBuff, bytesRead);
                     ResetEvent(stdErrEvt);
                     issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail);
                     }
                  }
               else
                  {
                  if (gShuttingDown)
                     {
                     log(LOG_DBG, "Woke with active child and service is terminating\n");

#define SHUTDOWN_MSG "Child process is being terminated because the service is shutting down\n"

                     writeChildOutFile(SHUTDOWN_MSG, strlen(SHUTDOWN_MSG));
                     terminateChild();
                     CloseHandle(hChildOut);
                     break;
                     }
                  }

               if (gShuttingDown)
                  {
                  break;
                  }

               }
            }
         }
      else if (gShuttingDown)
         {
         break;
         }

      CloseHandle(gStdOutReadHand);
      CloseHandle(gStdErrReadHand);

      }

   return 0;
   }

void writeChildOutFile(char *msg, int len)
   {
   DWORD bytesWritten;
   WriteFile(hChildOut, msg, len, &bytesWritten, 0);
   }


void terminateChild(void)
   {
   if (piProcInfo.dwProcessId != 0)
      {
      TerminateProcess(piProcInfo.hProcess, -1);
      CloseHandle(piProcInfo.hThread);
      CloseHandle(piProcInfo.hProcess);
      closeChildPipes();
      }
   }

void closeChildPipes(void)
   {
   CloseHandle(g_hChildStd_OUT_Wr);
   CloseHandle(g_hChildStd_ERR_Wr);
   }

void runProcess(void)
   {
   SECURITY_ATTRIBUTES saAttr;

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

   // Create a pipe for the child process's STDOUT.
   TCHAR szCmdline[]=TEXT("cmd.exe /C C:\\temp\\RunnerService.bat");
   STARTUPINFO siStartInfo;
   BOOL bSuccess = FALSE;

// Set up members of the PROCESS_INFORMATION structure.

   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

   g_hChildStd_OUT_Wr = CreateFile (STD_OUT_PIPE_NAME,
                FILE_WRITE_DATA,
                0,
                &saAttr,
                OPEN_EXISTING,
                0,
                NULL);

   if (g_hChildStd_OUT_Wr == INVALID_HANDLE_VALUE)
      {
      log(LOG_DBG, "Error creating child proc stdout file %d\n", GetLastError());
      }


   g_hChildStd_ERR_Wr = CreateFile (STD_ERR_PIPE_NAME,
                FILE_WRITE_DATA,
                0,
                &saAttr,
                OPEN_EXISTING,
                0,
                NULL);

   if (g_hChildStd_ERR_Wr == INVALID_HANDLE_VALUE)
      {
      log(LOG_DBG, "Error creating child proc stderr file %d\n", GetLastError());
      }


// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.

   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO);
   siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
   siStartInfo.hStdError = g_hChildStd_ERR_Wr;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

// Create the child process.

   bSuccess = 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

   }


void CreateChildOutFile(void)
   {
   SYSTEMTIME st;
   SECURITY_ATTRIBUTES sa;
   char fName[_MAX_PATH];

   InitSAPtr(&sa);

   GetLocalTime(&st);

   sprintf(fName, "C:\\TEMP\\runsvcchild_%02d_%02d_%02d_%04d.out", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

   hChildOut = CreateFile(fName, GENERIC_WRITE, FILE_SHARE_READ, &sa,  CREATE_ALWAYS,  FILE_ATTRIBUTE_NORMAL, 0);
   }

void issueRead(HANDLE hFile, OVERLAPPED *overLapped, char *buf, DWORD *dwRead)
   {
   //log(LOG_DBG, "Start of issueRead, hfile %08x, ovl is %08x\n", hFile, overLapped);
   BOOL brc = ReadFile(hFile, buf, 4096, dwRead, overLapped);
   if (!brc)
      {
      DWORD dwle = GetLastError();
      if (dwle != ERROR_IO_PENDING)
         {
         log(LOG_DBG, "Error %d on ReadFile\n", dwle);
         }
      }
   else
      {
      // log(LOG_DBG, "Read issued\n");
      }
   }  
查看更多
倾城 Initia
3楼-- · 2019-02-17 15:23

I think you did everything right. But cmd.exe prints nothing or very little amount of data after start and your ReadFile blocks. If you move your cycle

while (true) {
    bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
    if (!bStatus || dwRead == 0) {
        break;
    }
    aBuf[dwRead] = '\0';
    printf("%s\n", aBuf);
}

into background thread and run other cycle which will read your input and send it to cmd.exe, I think you can see any effect. Either you can make read buffer smaller (16 bytes e.g.).

查看更多
干净又极端
4楼-- · 2019-02-17 15:27

The subtle way out of your problem is to make sure you close the ends of the pipe you don't need:

    Us                                          Child
+------------------+                        +---------------+
|                  |                        |               |
|         g_hChildStd_IN_Wr----->g_hChildStd_IN_Rd          | 
|                  |                        |               | 
|       g_hChildStd_OUT_Rd<------g_hChildStd_OUT_Wr         |
|                  |                        |               |
+------------------+                        +---------------+

Your parent process only needs one end of each pipe:

  • writable end of the child input pipe
  • readable end of the child output pipe

Once you've launched your child process: be sure to close those ends of the pipe you no longer need.

result = CreateProcess(...);

//CreateProcess demands that we close these two populated handles when we're done with them. We're done with them.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

/*
   We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
   We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
   The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the console app.
   When the app closes, it will close the pipe, and ReadFile will return code 109 (The pipe has been ended).
   That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits)
*/
CloseHandle(g_hChildStd_OUT_Wr);
g_hChildStd_OUT_Wr = 0;
CloseHandle(g_hChildStd_IN_Rd);
g_hChildStd_OUT_Wr = 0;

The common problem with most solutions is that people try to wait on a process handle. There are many problems with this; the main one being that if you wait for the child the terminate, the child will never be able to terminate.

If the child is trying to send you output through the pipe, and you're INFINITE waiting, you're not emptying your end of the pipe. Eventually the pipe becomes full. When the child tries to write to a pipe that it full, the WriteFile call waits for the pipe to have some room. As a result the child process will never terminate; you've deadlocked everything.

The correct solution comes by simply reading from the pipe. Once the child process terminates, it will CloseHandle it's end of the pipe. The next time you try to read from the pipe you'll be told the pipe has been closed (ERROR_BROKEN_PIPE). That's how you know the process is done and you have no more stuff to read; all without a dangerous MsgWaitForSingleObject, that are error-prone to use correctly, and causes the very bug you want to avoid.

String outputText = "";

//Read will return when the buffer is full, or if the pipe on the other end has been broken
while (ReadFile(stdOutRead, aBuf, Length(aBuf), &bytesRead, null)
   outputText = outputText + Copy(aBuf, 1, bytesRead);

//ReadFile will either tell us that the pipe has closed, or give us an error
DWORD le = GetLastError;

//And finally cleanup
CloseHandle(g_hChildStd_IN_Wr);
CloseHandle(g_hChildStd_OUT_Rd);

if (le != ERROR_BROKEN_PIPE) //"The pipe has been ended."
   RaiseLastOSError(le);
查看更多
够拽才男人
5楼-- · 2019-02-17 15:34

I too have same scenario. in my case from Lib, need to execute internal exe and read output. The following works without any issues.

  void executeCMDInNewProcessAndReadOutput(LPSTR lpCommandLine)
    {
        STARTUPINFO si;
        SECURITY_ATTRIBUTES sa;
        PROCESS_INFORMATION pi;
        HANDLE g_hChildStd_IN_Rd, g_hChildStd_OUT_Wr, g_hChildStd_OUT_Rd, g_hChildStd_IN_Wr;  //pipe handles
        char buf[1024];           //i/o buffer

        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.bInheritHandle = TRUE;
        sa.lpSecurityDescriptor = NULL;

        if (CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0))   //create stdin pipe
        {
            if (CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0))  //create stdout pipe
            {

                //set startupinfo for the spawned process
                /*The dwFlags member tells CreateProcess how to make the process.
                STARTF_USESTDHANDLES: validates the hStd* members.
                STARTF_USESHOWWINDOW: validates the wShowWindow member*/
                GetStartupInfo(&si);

                si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
                si.wShowWindow = SW_HIDE;
                //set the new handles for the child process
                si.hStdOutput = g_hChildStd_OUT_Wr;
                si.hStdError = g_hChildStd_OUT_Wr;
                si.hStdInput = g_hChildStd_IN_Rd;

                //spawn the child process
                if (CreateProcess(NULL, lpCommandLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE,
                    NULL, NULL, &si, &pi))
                {
                    unsigned long bread;   //bytes read
                    unsigned long avail;   //bytes available
                    memset(buf, 0, sizeof(buf));

                    for (;;)
                    {
                        PeekNamedPipe(g_hChildStd_OUT_Rd, buf, 1023, &bread, &avail, NULL);
                        //check to see if there is any data to read from stdout
                        if (bread != 0)
                        {
                            if (ReadFile(g_hChildStd_OUT_Rd, buf, 1023, &bread, NULL))
                            {
                                break;
                            }
                        }
                    }

                    //clean up all handles
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);
                    CloseHandle(g_hChildStd_IN_Rd);
                    CloseHandle(g_hChildStd_OUT_Wr);
                    CloseHandle(g_hChildStd_OUT_Rd);
                    CloseHandle(g_hChildStd_IN_Wr);
                }
                else
                {
                    CloseHandle(g_hChildStd_IN_Rd);
                    CloseHandle(g_hChildStd_OUT_Wr);
                    CloseHandle(g_hChildStd_OUT_Rd);
                    CloseHandle(g_hChildStd_IN_Wr);
                }
            }
            else
            {
                CloseHandle(g_hChildStd_IN_Rd);
                CloseHandle(g_hChildStd_IN_Wr);
            }
        }
    }
查看更多
登录 后发表回答