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);
}
}
program doesn't stop because
ReadFile
is blocking it.
but why ReadFile
must 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 ReadFile
return (error will be STATUS_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 and ReadFile
never 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_LIST
attribute when we start process with inherited handles, for exactly control what will be inherited
the 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
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
ULONG Start(_In_ PCWSTR lpApplicationName,
_In_ PWSTR lpCommandLine,
_Out_ PHANDLE hReadPipe)
{
STARTUPINFOEXW si = { { sizeof(si) } };
SIZE_T s = 0;
ULONG dwError;
while (ERROR_INSUFFICIENT_BUFFER == (dwError = BOOL_TO_ERROR(InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &s))))
{
if (si.lpAttributeList)
{
break;
}
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(s);
}
if (NOERROR == dwError && NOERROR == (dwError = BOOL_TO_ERROR(
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&si.StartupInfo.hStdOutput, sizeof(si.StartupInfo.hStdOutput), 0, 0))))
{
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
if (NOERROR == (dwError = BOOL_TO_ERROR(CreatePipe(hReadPipe, &si.StartupInfo.hStdOutput, &sa, 0))))
{
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
si.StartupInfo.hStdError = si.StartupInfo.hStdOutput;
PROCESS_INFORMATION pi;
if (NOERROR == (dwError = BOOL_TO_ERROR(CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, TRUE,
EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS|CREATE_NO_WINDOW,
0, 0, &si.StartupInfo, &pi))))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(si.StartupInfo.hStdOutput);
return NOERROR;
}
CloseHandle(si.StartupInfo.hStdOutput);
CloseHandle(*hReadPipe);
}
}
return dwError;
}
void IpconfigDemo()
{
HANDLE hReadPipe;
union {
WCHAR ApplicationName[MAX_PATH];
char buf[0x100];
};
if (SearchPathW(0, L"ipconfig.exe", 0, _countof(ApplicationName), ApplicationName, 0))
{
SetEnvironmentVariableW(L"OutputEncoding", L"Ansi");//Unicode // UTF-8
if (NOERROR == Start(ApplicationName, 0, &hReadPipe))
{
OVERLAPPED ov{};
ULONG n;
while (ReadFile(hReadPipe, buf, sizeof(buf), &n, &ov))
{
buf[n] = 0;
DbgPrint(buf);
}
RtlGetLastNtStatus();
GetLastError();
CloseHandle(hReadPipe);
}
}
}
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:
ULONG Start(_In_ PCWSTR lpApplicationName,
_In_ PWSTR lpCommandLine,
_Out_ PHANDLE hReadPipe,
_Out_ PHANDLE hWritePipe)
{
STARTUPINFOEXW si = { { sizeof(si) } };
SIZE_T s = 0;
ULONG dwError;
while (ERROR_INSUFFICIENT_BUFFER == (dwError = BOOL_TO_ERROR(InitializeProcThreadAttributeList(si.lpAttributeList, 2, 0, &s))))
{
if (si.lpAttributeList)
{
break;
}
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(s);
}
HANDLE h[2];
if (NOERROR == dwError && NOERROR == (dwError = BOOL_TO_ERROR(
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, h, sizeof(h), 0, 0))))
{
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
if (NOERROR == (dwError = BOOL_TO_ERROR(CreatePipe(&si.StartupInfo.hStdInput, hWritePipe, &sa, 0))))
{
if (NOERROR == (dwError = BOOL_TO_ERROR(CreatePipe(hReadPipe, &si.StartupInfo.hStdOutput, &sa, 0))))
{
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
si.StartupInfo.hStdError = si.StartupInfo.hStdOutput;
h[0] = si.StartupInfo.hStdInput, h[1] = si.StartupInfo.hStdOutput;
PROCESS_INFORMATION pi;
if (NOERROR == (dwError = BOOL_TO_ERROR(CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, TRUE,
EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS|CREATE_NO_WINDOW,
0, 0, &si.StartupInfo, &pi))))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(si.StartupInfo.hStdOutput);
CloseHandle(si.StartupInfo.hStdInput);
return NOERROR;
}
CloseHandle(si.StartupInfo.hStdOutput);
CloseHandle(*hReadPipe);
}
CloseHandle(si.StartupInfo.hStdInput);
CloseHandle(*hWritePipe);
}
}
return dwError;
}
void CmdDemo()
{
HANDLE hReadPipe, hWritePipe;
union {
WCHAR ApplicationName[MAX_PATH];
char buf[0x100];
};
if (SearchPathW(0, L"cmd.exe", 0, _countof(ApplicationName), ApplicationName, 0))
{
if (NOERROR == Start(ApplicationName, 0, &hReadPipe, &hWritePipe))
{
OVERLAPPED ov{};
ULONG n;
static const char cmd[] = "dir\r\nexit\r\n";
if (WriteFile(hWritePipe, cmd, sizeof(cmd) - 1, &n, &ov))
{
while (ReadFile(hReadPipe, buf, sizeof(buf), &n, &ov))
{
buf[n] = 0;
DbgPrint(buf);
}
RtlGetLastNtStatus();
GetLastError();
}
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
}
}
}
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:
struct __declspec(novtable) uObject
{
HANDLE _M_hFile = 0;
LONG _M_dwRefCount = 1;
virtual ~uObject()
{
if (_M_hFile)
{
CloseHandle(_M_hFile);
}
}
virtual void OnIoComplete(ULONG dwError, ULONG dwBytes, ULONG code, PVOID buf) = 0;
void AddRef()
{
InterlockedIncrementNoFence(&_M_dwRefCount);
}
void Release()
{
if (!InterlockedDecrement(&_M_dwRefCount))
{
delete this;
}
}
};
struct uIRP : OVERLAPPED
{
ULONG _M_code;
uObject* _M_pObj;
UCHAR _M_buf[];
uIRP(ULONG code, uObject* pObj) : _M_code(code), _M_pObj(pObj)
{
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
pObj->AddRef();
}
~uIRP()
{
_M_pObj->Release();
}
VOID OnIoComplete(
_In_ DWORD dwErrorCode,
_In_ DWORD dwNumberOfBytesTransfered
)
{
_M_pObj->OnIoComplete(dwErrorCode, dwNumberOfBytesTransfered, _M_code, _M_buf);
delete this;
}
static VOID WINAPI _S_OnIoComplete(
_In_ DWORD dwErrorCode,
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<uIRP*>(lpOverlapped)->OnIoComplete(RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
static ULONG Bind(HANDLE hFile)
{
return BOOL_TO_ERROR(BindIoCompletionCallback(hFile, _S_OnIoComplete, 0));
}
void* operator new(size_t s, ULONG cb)
{
return LocalAlloc(LMEM_FIXED, s + cb);
}
void operator delete(void* p)
{
LocalFree(p);
}
};
uObject
is abstract. need implemet OnIoComplete
for concrete object. for pipes (such simply case) we can do next implementation:
struct uPipe : public uObject
{
enum { opWrite = 'wwww', opRead = 'rrrr' };
HANDLE _M_hEvent = CreateEvent(0, 0, 0, 0);
~uPipe()
{
CloseHandle(_M_hEvent);
}
void Write(
_In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite)
{
if (uIRP* irp = new(0) uIRP(opWrite, this))
{
switch (ULONG dwError = BOOL_TO_ERROR(WriteFile(_M_hFile, lpBuffer, nNumberOfBytesToWrite, 0, irp)))
{
case NOERROR:
case ERROR_IO_PENDING:
break;
default:
irp->OnIoComplete(dwError, 0);
}
}
}
void Read(ULONG cb)
{
if (uIRP* irp = new(cb+1) uIRP(opRead, this))
{
switch (ULONG dwError = BOOL_TO_ERROR(ReadFile(_M_hFile, irp->_M_buf, cb, 0, irp)))
{
case NOERROR:
case ERROR_IO_PENDING:
break;
default:
irp->OnIoComplete(dwError, 0);
}
}
}
void OnRead(PSTR buf, ULONG dwBytes)
{
buf[dwBytes] = 0;
DbgPrint(buf);
}
virtual void OnIoComplete(ULONG dwError, ULONG dwBytes, ULONG code, PVOID buf)
{
if (dwError)
{
SetEvent(_M_hEvent);
return;
}
switch (code)
{
case opRead:
OnRead((PSTR)buf, dwBytes);
Read(0x100);
break;
case opWrite:
break;
default:
__debugbreak();
}
}
};
then we need own implementation of CreatePipe
api. the standard api create full synchronous pipe pair. but we need asynchronous.
enum {
FLAG_PIPE_CLIENT_SYNCHRONOUS = 0x01,
FLAG_PIPE_CLIENT_INHERIT = 0x02,
FLAG_PIPE_SERVER_SYNCHRONOUS = 0x04,
FLAG_PIPE_SERVER_INHERIT = 0x8,
};
#ifndef FILE_SHARE_VALID_FLAGS
#define FILE_SHARE_VALID_FLAGS (FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE)
#endif
NTSTATUS CreatePipeAnonymousPair(PHANDLE phServerPipe, PHANDLE phClientPipe, ULONG Flags, DWORD nInBufferSize)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
UNICODE_STRING NamedPipe;
RtlInitUnicodeString(&NamedPipe, L"\\Device\\NamedPipe\\");
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &NamedPipe, OBJ_CASE_INSENSITIVE };
NTSTATUS status;
if (0 <= (status = NtOpenFile(&hFile, SYNCHRONIZE, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0)))
{
oa.RootDirectory = hFile;
static const UNICODE_STRING empty = {};
oa.Attributes = Flags & FLAG_PIPE_SERVER_INHERIT ? OBJ_INHERIT : 0;
oa.ObjectName = const_cast<PUNICODE_STRING>(&empty);
LARGE_INTEGER time = { 0, (LONG)MINLONG };
if (0 <= (status = ZwCreateNamedPipeFile(phServerPipe,
FILE_READ_ATTRIBUTES|FILE_READ_DATA|
FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA|
FILE_CREATE_PIPE_INSTANCE,
&oa, &iosb, FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_CREATE,
Flags & FLAG_PIPE_SERVER_SYNCHRONOUS ? FILE_SYNCHRONOUS_IO_NONALERT : 0,
FILE_PIPE_BYTE_STREAM_TYPE, FILE_PIPE_BYTE_STREAM_MODE,
FILE_PIPE_QUEUE_OPERATION, 1, nInBufferSize, nInBufferSize, &time)))
{
oa.RootDirectory = *phServerPipe;
oa.Attributes = Flags & FLAG_PIPE_CLIENT_INHERIT ? OBJ_INHERIT : 0;
if (0 > (status = NtOpenFile(phClientPipe, SYNCHRONIZE|FILE_READ_ATTRIBUTES|FILE_READ_DATA|
FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
Flags & FLAG_PIPE_CLIENT_SYNCHRONOUS ? FILE_SYNCHRONOUS_IO_NONALERT : 0)))
{
NtClose(oa.RootDirectory);
*phServerPipe = 0;
}
}
NtClose(hFile);
}
return status;
}
and now we can use next code:
ULONG StartA(_In_ PCWSTR lpApplicationName,
_In_ PWSTR lpCommandLine,
_Out_ PHANDLE hPipe)
{
STARTUPINFOEXW si = { { sizeof(si) } };
SIZE_T s = 0;
ULONG dwError;
while (ERROR_INSUFFICIENT_BUFFER == (dwError = BOOL_TO_ERROR(InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &s))))
{
if (si.lpAttributeList)
{
break;
}
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)alloca(s);
}
if (NOERROR == dwError && NOERROR == (dwError = BOOL_TO_ERROR(
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&si.StartupInfo.hStdOutput, sizeof(si.StartupInfo.hStdOutput), 0, 0))))
{
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
if (STATUS_SUCCESS == (dwError = CreatePipeAnonymousPair(hPipe, &si.StartupInfo.hStdError,
FLAG_PIPE_CLIENT_SYNCHRONOUS|FLAG_PIPE_CLIENT_INHERIT, 0)))
{
if (NOERROR == (dwError = uIRP::Bind(*hPipe)))
{
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
si.StartupInfo.hStdInput = si.StartupInfo.hStdOutput = si.StartupInfo.hStdError;
PROCESS_INFORMATION pi;
if (NOERROR == BOOL_TO_ERROR(CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, TRUE,
EXTENDED_STARTUPINFO_PRESENT|DETACHED_PROCESS|CREATE_NO_WINDOW,
0, 0, &si.StartupInfo, &pi)))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(si.StartupInfo.hStdOutput);
return NOERROR;
}
}
CloseHandle(si.StartupInfo.hStdOutput);
CloseHandle(*hPipe);
}
else
{
dwError = RtlNtStatusToDosError(dwError);
}
}
return dwError;
}
void CmdDemoA()
{
WCHAR ApplicationName[MAX_PATH];
if (SearchPathW(0, L"cmd.exe", 0, _countof(ApplicationName), ApplicationName, 0))
{
if (uPipe* pipe = new uPipe)
{
if (NOERROR == StartA(ApplicationName, 0, &pipe->_M_hFile))
{
static const char cmd[] = "dir\r\nexit\r\n";
pipe->Write(cmd, sizeof(cmd) - 1);
pipe->Read(0x100);
//... any code ...
WaitForSingleObject(pipe->_M_hEvent, INFINITE);
}
pipe->Release();
}
}
}