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;
}
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.
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
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.).
The subtle way out of your problem is to make sure you close the ends of the pipe you don't need:
Your parent process only needs one end of each pipe:
Once you've launched your child process: be sure to close those ends of the pipe you no longer need.
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, theWriteFile
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.I too have same scenario. in my case from Lib, need to execute internal exe and read output. The following works without any issues.