I want to write a program that executes a cmd or powershell command. To achive this I create a process with CreateProcessA and use pipes for Input / Output. My code works fine but after the command (ex.: "ipconfig") is executed my program doesn't stop because ReadFile is blocking it.
I tried to use ShellExecute but I don't know how to redirect the output so I would prefer a solution using CreateProcess and my existing code.
My code:
#include "execute_cmdpws.h"
#include <stdio.h>
bool create_pipe(PHANDLE stdInWrite, PHANDLE stdInRead, PHANDLE stdOutWrite, PHANDLE stdOutRead){
bool status = false;
int iResult;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
iResult = CreatePipe(stdInRead, stdInWrite, &sa, 0);
if (!iResult){
CloseHandle(*stdInRead);
CloseHandle(*stdInWrite);
goto end;
}
iResult = CreatePipe(stdOutRead, stdOutWrite, &sa, 0);
if (!iResult) {
CloseHandle(*stdInRead);
CloseHandle(*stdInWrite);
CloseHandle(*stdOutRead);
CloseHandle(*stdOutWrite);
goto end;
}
status = true;
end:
return status;
}
bool execute_command(char* shelltype, char* command, PHANDLE stdInWrite, PHANDLE stdInRead, PHANDLE stdOutWrite, PHANDLE stdOutRead){
bool status = false;
int iResult;
int buffer_size = 1024;
char buffer[buffer_size];
DWORD dwBytesRead;
STARTUPINFO sinfo;
memset(&sinfo, 0, sizeof(sinfo));
sinfo.cb = sizeof(sinfo);
sinfo.dwFlags = (STARTF_USESTDHANDLES);
sinfo.hStdInput = *stdInRead;
sinfo.hStdOutput = sinfo.hStdError = *stdOutWrite;
PROCESS_INFORMATION pinfo;
iResult = CreateProcessA(NULL, shelltype, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo);
if (!iResult){
goto end;
}
// Wait for the process to start
WaitForInputIdle(pinfo.hProcess, INFINITE);
if (!iResult){
goto end;
}
DWORD dwBytesWritten = 0;
iResult = WriteFile(*stdInWrite, command, strlen(command), &dwBytesWritten, NULL);
if (!iResult){
goto end;
}
// Close the write end of the pipes in the parent process, as they are not needed
CloseHandle(*stdInWrite);
// Read the output in a loop until there is no more data
while (true) {
iResult = ReadFile(*stdOutRead, buffer, buffer_size - 1, &dwBytesRead, NULL);
if (!iResult || dwBytesRead == 0) {
break; // No more data or error reading
}
buffer[dwBytesRead] = '\0'; // Null-terminate the buffer
printf("Output: %s", buffer);
memset(buffer, 0, buffer_size);
}
if (!iResult){
goto end;
}
CloseHandle(*stdOutRead);
status = true;
end:
TerminateProcess(pinfo.hProcess, 0);
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
// Close the read end of the pipes in the parent process
CloseHandle(*stdInRead);
CloseHandle(*stdOutWrite);
return status;
}
int main(){
HANDLE
stdInWrite,
stdInRead,
stdOutWrite,
stdOutRead;
bool res;
res = create_pipe(&stdInWrite, &stdInRead, &stdOutWrite, &stdOutRead);
if (!res) return 1;
res = execute_command("cmd.exe", "ipconfig\n", &stdInWrite, &stdInRead, &stdOutWrite, &stdOutRead);
if (!res) return 2;
return 0;
}
This is the last line of the output I get (It seems like it's expecting a input but I can't type anything):
Output: C:\path\to\my\folder>
Is there a solution for that or another approatch to execute a cmd / powershell command and redirect the output?
EDIT:
I now use this code and it works but it's not ideal. If someone has another idea, I'm still looking for solutions.
// Read the output in a loop until there is no more data
DWORD dwTotalBytesAvail;
DWORD dwBytesRead;
DWORD timeout = 1000;
DWORD start_time = GetTickCount();
while (true){
iResult = PeekNamedPipe(stdOutRead, NULL, 0, NULL, &dwTotalBytesAvail, NULL);
if (!iResult){
return 3;
}
if (dwTotalBytesAvail > 0){
iResult = ReadFile(stdOutRead, buffer, buffer_size - 1, &dwBytesRead, NULL);
if (!iResult || dwBytesRead == 0){
break; // No more data or error reading
}
buffer[dwBytesRead] = '\0'; // Null-terminate the buffer
printf("Output: %s", buffer);
memset(buffer, 0, buffer_size);
start_time = GetTickCount();
} else {
DWORD elapsed_time = GetTickCount() - start_time;
if (elapsed_time >= timeout){
break;
}
Sleep(100);
}
}
but why
ReadFilemust return ? if we use asynchronous handle - it return just always. but in case synchronous handle - api call can wait for input infinite long.in case when we use pipe handle - I/O operation will finish in case last handle to linked pipe end is closed. if linked pipe in child process - when process exit - all handles is closed and if will be no other handles - this will be last handle and
ReadFilereturn (error will beSTATUS_PIPE_BROKEN- The pipe operation has failed because the other end of the pipe has been closed ). but if we forget close linked pipe end in self process - last handle will be not closed andReadFilenever return.other fatal error - start cmd.exe . what sense start cmd.exe when you need start ipconfig.exe ? start it direct without any cmd.
always need use
PROC_THREAD_ATTRIBUTE_HANDLE_LISTattribute when we start process with inherited handles, for exactly control what will be inheritedthe next - for what is need 2 pipe pair ?! really single pipe pair always can be enouth. pipe can be used for both read and write at once and not need additional pipe pair. but here exist one problem - if use synchronous files - all operation with it is sequential. new I/O not start, until previous not finished. and this can lead to deadlock. only because this frequently used 2 separate pipe pair - for read and write, for avoid possible deadlock. but if we use asynchronous pipes (how minimum from self side, other end can be synchronous) we never got deadlock and can use single pipe pair
in many cases, like ipconfig.exe we need only read output. we nothing need to write. so possible (and must) use only single pipe paire here, even in case full synchronous pipes (both ends is synchronous)
so minimal example for ipconfig.exe
here we use only sinle pipe pair for read (both ends is synchronous)
if we need more generic code, where need write commands too, in case synchronous pipes, code can be next:
and finally - example with asynchronous pipes (really our end is asynchronous and client end is synchronous)
for use asynchronous I/O we almost always need some lib/classes for wrap operations with handles and I/O
for current demo - very minimal code:
uObjectis abstract. need implemetOnIoCompletefor concrete object. for pipes (such simply case) we can do next implementation:then we need own implementation of
CreatePipeapi. the standard api create full synchronous pipe pair. but we need asynchronous.and now we can use next code: