Reputation: 59
I'm new to WIN32 API and could use some help figuring what am I missing in my code. My goal is to run a batch script (or PowerShell script), wait for completion, and then get the stdout / stderr upon completion.
Following this and this article on msdn about creating a process and redirecting stdout and stderr to a handle. I mashed the samples together and came up with the following code:
int execute_commnad(std::string& command,std::string& output,int& timeout_seconds) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
DWORD return_code = NULL;
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si,sizeof(si));
si.cb = sizeof(si);
si.hStdOutput = child_stdout_handle;
si.hStdError = child_stdout_handle;
si.dwFlags |= STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Connect Child stdout to parent_stdout_handle
if (!CreatePipe(&parent_stdout_handle,&child_stdout_handle,&saAttr,0))
{
output = "Could not create stdout pipe. Error: " + std::to_string(GetLastError());
close_handles();
return -1;
}
if (! SetHandleInformation(parent_stdout_handle,HANDLE_FLAG_INHERIT,0))
{
output = "Could not set handle information for stdout pipe. Error: " + std::to_string(GetLastError());
close_handles();
return -1;
}
// Create Process
if (!CreateProcess(NULL,LPSTR(command.c_str()),NULL,NULL,TRUE,0,NULL,NULL,&si,&pi)) {
output = "Failed to create process. Error: " + std::to_string(GetLastError());
close_handles();
return -1;
}
CloseHandle(child_stdout_handle);
// Wait for process to finish or timeout
if (timeout_seconds == NULL) {
WaitForSingleObject(pi.hProcess,INFINITE);
}
else {
WaitForSingleObject(pi.hProcess,DWORD(timeout_seconds * 1000));
}
if (!GetExitCodeProcess(pi.hProcess,&return_code)) {
output = "Failed to fetch exit code";
close_handles();
return -1;
}
if (STILL_ACTIVE == return_code) {
TerminateProcess(sub_process,return_code);
output = "Command did not finish within defined timeout threshold";
}
else if (STATUS_PENDING == return_code) {
output = "process is pending";
}
else {
DWORD dwRead;
CHAR chBuf[4096];
BOOL bSuccess = FALSE;
for (;;) {
bSuccess = ReadFile(parent_stdout_handle,chBuf,4096,&dwRead,NULL);
if (! bSuccess || dwRead == 0) break;
output = *reinterpret_cast<std::string*>(parent_stdout_handle);
}
}
close_handles();
return 0;
};
void close_handles(void) {
CloseHandle(parent_stdout_handle);
CloseHandle(child_stdout_handle);
CloseHandle(sub_process);
};
Before executing the function above I create the .bat file using std::ofstream
containing "echo hello" and save the FQN of the file into variable named file_name
and run the following code:
int rc = 0;
std::string cmd = "cmd.exe /c " + file_name + " " + script_params;
rc = execute_commnad(cmd,this->output,timeout_in_seconds);
When debugging these are the values I get after GetExitCodeProcess
:
I have tried replacing cmd.exe
with the FQN, but same result, tried running something as simple as cmd.exe /c "echo hello"
and still returned return_code 1.
I've been struggling for the last 2 days in finding the reason for it, but to no avail.
Aside for the ineffective code (performance-wise) does anyone has a suggestion to what might be the problem?
Thanks in advance!
Upvotes: 0
Views: 478
Reputation: 7170
Make sure to initialize the child_stdout_handle
before passing the handle to CreateProcess
.
In addition, you need to print the value of chBuf
you read in ReadFile
, instead of parent_stdout_handle
. Converting file handle to string
is meaningless, you could try:
for (;;) {
bSuccess = ReadFile(parent_stdout_handle, chBuf, 4096, &dwRead, NULL);
if (!bSuccess || dwRead == 0) break;
chBuf[dwRead] = '\0';
output += chBuf;
}
cout << output << endl;
Upvotes: 2