Capture Output of Spawned Process to string - c++

Background:
I'm working on a program that needs to be able to capture the stdout, stderr and return values of a program. Ideally, I would like to capture these in a string that I store inside of my object that holds details of the process. I currently have some code that works by saving the output into a file using some (in my opinion) archaic C file handle magic. Any time I want to output the results, I open up that file and I print the contents.
Sometimes (when a process I spawn is left running) the next execution of my executable will break down because it cannot open the file for writing.
Problem Statement:
I'm looking for a way to save the output from stdout of a created process in windows to one string and the stderr to another in a safer, more modern fashion. That way I could print those contents any time I feel like outputting the result of each created process.
My ugly code:
main chunk-
int stdoutold = _dup(_fileno(stdout)); //make a copy of stdout
int stderrold = _dup(_fileno(stdout)); //make a copy of stderr
FILE *f;
if(!fopen_s(&f, "name_of_my_file", "w")){ //make sure I can write to the file
_dup2(_fileno(f), _fileno(stdout)); //make stdout point to f
_dup2(_fileno(f), _fileno(stderr)); //make stderr point to f
fork("command_I_want_to_run", &pi); //run my fake fork (see below)
}
else{
...//error handling
}
_close(_fileno(stdout)); //close tainted stdout
_close(_fileno(stderr)); //close tainted stderr
_close(_fileno(f)); //close f
_dup2(stdoutold, _fileno(stdout)); //fix stdout
_dup2(stderrold, _fileno(stderr)); //fix stderr
fork- (you can think of this as just CreateProcess, but just in case anyone needs to see what happens here)
int fork(std::string s, PROCESS_INFORMATION* pi){
char infoBuf[INFO_BUFFER_SIZE];
int bufCharCount =
ExpandEnvironmentStrings(s.c_str(), infoBuf, INFO_BUFFER_SIZE );
...
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( pi, sizeof(*pi) );
LPSTR str = const_cast<char *>(infoBuf);
if(!CreateProcess(NULL,
str,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&si,
pi)
){
int err = GetLastError();
printf("CreateProcess failed (%d).\n", err);
CloseHandle((*pi).hProcess);
CloseHandle((*pi).hThread);
return err;
}
return 0;
}
Notes:
I'm using VS 2010
I want to remain using multiple processes, not threads because I need what I run to have the freedom of its own process
Edit:
An extra note: I also try to wait for the process to finish right after calling the function that runs the code given, so the results of stdout and stderr are available to me at that time.

Eddy Luten's answer led me in a good direction, but the MSDN documentation (while elaborate) had some issues. Mainly, you need to ensure you close all handles you don't use. Also it just has code it expects the user to understand.
So instead, here's my wall of code I expect people to just understand :D
#include <string>
#include <iostream>
#include <windows.h>
#include <stdio.h>
#pragma warning( disable : 4800 ) // stupid warning about bool
#define BUFSIZE 4096
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hChildStd_ERR_Rd = NULL;
HANDLE g_hChildStd_ERR_Wr = NULL;
PROCESS_INFORMATION CreateChildProcess(void);
void ReadFromPipe(PROCESS_INFORMATION);
int main(int argc, char *argv[]){
SECURITY_ATTRIBUTES sa;
printf("\n->Start of parent execution.\n");
// Set the bInheritHandle flag so pipe handles are inherited.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDERR.
if ( ! CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &sa, 0) ) {
exit(1);
}
// Ensure the read handle to the pipe for STDERR is not inherited.
if ( ! SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0) ){
exit(1);
}
// Create a pipe for the child process's STDOUT.
if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0) ) {
exit(1);
}
// Ensure the read handle to the pipe for STDOUT is not inherited
if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) ){
exit(1);
}
// Create the child process.
PROCESS_INFORMATION piProcInfo = CreateChildProcess();
// Read from pipe that is the standard output for child process.
printf( "\n->Contents of child process STDOUT:\n\n", argv[1]);
ReadFromPipe(piProcInfo);
printf("\n->End of parent execution.\n");
// The remaining open handles are cleaned up when this process terminates.
// To avoid resource leaks in a larger application,
// close handles explicitly.
return 0;
}
// Create a child process that uses the previously created pipes
// for STDERR and STDOUT.
PROCESS_INFORMATION CreateChildProcess(){
// Set the text I want to run
char szCmdline[]="test --log_level=all --report_level=detailed";
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
bool bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDERR and STDOUT handles for redirection.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_ERR_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_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
CloseHandle(g_hChildStd_ERR_Wr);
CloseHandle(g_hChildStd_OUT_Wr);
// If an error occurs, exit the application.
if ( ! bSuccess ) {
exit(1);
}
return piProcInfo;
}
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT.
// Stop when there is no more data.
void ReadFromPipe(PROCESS_INFORMATION piProcInfo) {
DWORD dwRead;
CHAR chBuf[BUFSIZE];
bool bSuccess = FALSE;
std::string out = "", err = "";
for (;;) {
bSuccess=ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if( ! bSuccess || dwRead == 0 ) break;
std::string s(chBuf, dwRead);
out += s;
}
dwRead = 0;
for (;;) {
bSuccess=ReadFile( g_hChildStd_ERR_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if( ! bSuccess || dwRead == 0 ) break;
std::string s(chBuf, dwRead);
err += s;
}
std::cout << "stdout:" << out << std::endl;
std::cout << "stderr:" << err << std::endl;
}

Shawn Blakesley code is good rework of Microsoft sample code but it has a bit of a problem when there is massive stdout and stderr interleaved streams that are out of order. And some handles are leaked (which is OK for the sample code). Having background thread and PeekNamedPipe() calls makes sure the the code behave more similar to POSIX system call:
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
#ifdef __cplusplus
#define BEGIN_C extern "C" {
#define END_C } // extern "C"
#define null nullptr
#else
#define BEGIN_C
#define END_C
#define null ((void*)0)
#endif
BEGIN_C
int system_np(const char* command, int timeout_milliseconds,
char* stdout_data, int stdout_data_size,
char* stderr_data, int stderr_data_size, int* exit_code);
typedef struct system_np_s {
HANDLE child_stdout_read;
HANDLE child_stderr_read;
HANDLE reader;
PROCESS_INFORMATION pi;
const char* command;
char* stdout_data;
int stdout_data_size;
char* stderr_data;
int stderr_data_size;
int* exit_code;
int timeout; // timeout in milliseconds or -1 for INIFINTE
} system_np_t;
static char stdout_data[16 * 1024 * 1024];
static char stderr_data[16 * 1024 * 1024];
int main(int argc, char *argv[]) {
int bytes = 1;
for (int i = 1; i < argc; i++) {
bytes += (int)strlen(argv[i]) + 1;
}
char* command = (char*)alloca(bytes);
command[0] = 0;
char* p = command;
for (int i = 1; i < argc; i++) {
int n = (int)strlen(argv[i]);
memcpy(p, argv[i], n); p += n;
*p = (i == argc - 1) ? 0x00 : 0x20;
p++;
}
int exit_code = 0;
if (command[0] == 0) {
command = (char*)"cmd.exe /c \"dir /w /b\"";
}
int r = system_np(command, 100 * 1000, stdout_data, sizeof(stdout_data), stderr_data, sizeof(stderr_data), &exit_code);
if (r != 0) {
fprintf(stderr, "system_np failed: %d 0x%08x %s", r, r, strerror(r));
return r;
} else {
fwrite(stdout_data, strlen(stdout_data), 1, stdout);
fwrite(stderr_data, strlen(stderr_data), 1, stderr);
return exit_code;
}
}
static int peek_pipe(HANDLE pipe, char* data, int size) {
char buffer[4 * 1024];
DWORD read = 0;
DWORD available = 0;
bool b = PeekNamedPipe(pipe, null, sizeof(data), null, &available, null);
if (!b) {
return -1;
} else if (available > 0) {
int bytes = min(sizeof(buffer), available);
b = ReadFile(pipe, buffer, bytes, &read, null);
if (!b) {
return -1;
}
if (data != null && size > 0) {
int n = min(size - 1, (int)read);
memcpy(data, buffer, n);
data[n + 1] = 0; // always zero terminated
return n;
}
}
return 0;
}
static DWORD WINAPI read_from_all_pipes_fully(void* p) {
system_np_t* system = (system_np_t*)p;
unsigned long long milliseconds = GetTickCount64(); // since boot time
char* out = system->stdout_data != null && system->stdout_data_size > 0 ? system->stdout_data : null;
char* err = system->stderr_data != null && system->stderr_data_size > 0 ? system->stderr_data : null;
int out_bytes = system->stdout_data != null && system->stdout_data_size > 0 ? system->stdout_data_size - 1 : 0;
int err_bytes = system->stderr_data != null && system->stderr_data_size > 0 ? system->stderr_data_size - 1 : 0;
for (;;) {
int read_stdout = peek_pipe(system->child_stdout_read, out, out_bytes);
if (read_stdout > 0 && out != null) { out += read_stdout; out_bytes -= read_stdout; }
int read_stderr = peek_pipe(system->child_stderr_read, err, err_bytes);
if (read_stderr > 0 && err != null) { err += read_stderr; err_bytes -= read_stderr; }
if (read_stdout < 0 && read_stderr < 0) { break; } // both pipes are closed
unsigned long long time_spent_in_milliseconds = GetTickCount64() - milliseconds;
if (system->timeout > 0 && time_spent_in_milliseconds > system->timeout) { break; }
if (read_stdout == 0 && read_stderr == 0) { // nothing has been read from both pipes
HANDLE handles[2] = {system->child_stdout_read, system->child_stderr_read};
WaitForMultipleObjects(2, handles, false, 1); // wait for at least 1 millisecond (more likely 16)
}
}
if (out != null) { *out = 0; }
if (err != null) { *err = 0; }
return 0;
}
static int create_child_process(system_np_t* system) {
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true;
sa.lpSecurityDescriptor = null;
HANDLE child_stdout_write = INVALID_HANDLE_VALUE;
HANDLE child_stderr_write = INVALID_HANDLE_VALUE;
if (!CreatePipe(&system->child_stderr_read, &child_stderr_write, &sa, 0) ) {
return GetLastError();
}
if (!SetHandleInformation(system->child_stderr_read, HANDLE_FLAG_INHERIT, 0) ){
return GetLastError();
}
if (!CreatePipe(&system->child_stdout_read, &child_stdout_write, &sa, 0) ) {
return GetLastError();
}
if (!SetHandleInformation(system->child_stdout_read, HANDLE_FLAG_INHERIT, 0) ){
return GetLastError();
}
// Set the text I want to run
STARTUPINFO siStartInfo = {0};
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = child_stderr_write;
siStartInfo.hStdOutput = child_stdout_write;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow = SW_HIDE;
bool b = CreateProcessA(null,
(char*)system->command,
null, // process security attributes
null, // primary thread security attributes
true, // handles are inherited
CREATE_NO_WINDOW, // creation flags
null, // use parent's environment
null, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&system->pi); // receives PROCESS_INFORMATION
int err = GetLastError();
CloseHandle(child_stderr_write);
CloseHandle(child_stdout_write);
if (!b) {
CloseHandle(system->child_stdout_read); system->child_stdout_read = INVALID_HANDLE_VALUE;
CloseHandle(system->child_stderr_read); system->child_stderr_read = INVALID_HANDLE_VALUE;
}
return b ? 0 : err;
}
int system_np(const char* command, int timeout_milliseconds,
char* stdout_data, int stdout_data_size,
char* stderr_data, int stderr_data_size, int* exit_code) {
system_np_t system = {0};
if (exit_code != null) { *exit_code = 0; }
if (stdout_data != null && stdout_data_size > 0) { stdout_data[0] = 0; }
if (stderr_data != null && stderr_data_size > 0) { stderr_data[0] = 0; }
system.timeout = timeout_milliseconds > 0 ? timeout_milliseconds : -1;
system.command = command;
system.stdout_data = stdout_data;
system.stderr_data = stderr_data;
system.stdout_data_size = stdout_data_size;
system.stderr_data_size = stderr_data_size;
int r = create_child_process(&system);
if (r == 0) {
system.reader = CreateThread(null, 0, read_from_all_pipes_fully, &system, 0, null);
if (system.reader == null) { // in theory should rarely happen only when system super low on resources
r = GetLastError();
TerminateProcess(system.pi.hProcess, ECANCELED);
} else {
bool thread_done = WaitForSingleObject(system.pi.hThread, timeout_milliseconds) == 0;
bool process_done = WaitForSingleObject(system.pi.hProcess, timeout_milliseconds) == 0;
if (!thread_done || !process_done) {
TerminateProcess(system.pi.hProcess, ETIME);
}
if (exit_code != null) {
GetExitCodeProcess(system.pi.hProcess, (DWORD*)exit_code);
}
CloseHandle(system.pi.hThread);
CloseHandle(system.pi.hProcess);
CloseHandle(system.child_stdout_read); system.child_stdout_read = INVALID_HANDLE_VALUE;
CloseHandle(system.child_stderr_read); system.child_stderr_read = INVALID_HANDLE_VALUE;
WaitForSingleObject(system.reader, INFINITE); // join thread
CloseHandle(system.reader);
}
}
if (stdout_data != null && stdout_data_size > 0) { stdout_data[stdout_data_size - 1] = 0; }
if (stderr_data != null && stderr_data_size > 0) { stderr_data[stderr_data_size - 1] = 0; }
return r;
}
END_C

You'll have to use pipes to capture the contents of your process's stdout stream. There's an elaborate example on MSDN on how to accomplish this:
MSDN: Creating a Child Process with Redirected Input and Output

Related

C++ on Windows: Pipe to stdin not properly transferring data + ReadFile stucks when reading from pipe

I tried to create a pwntools-like program for windows. I implemented a send and recv to send data to stdin of a and to receive data from stdout. I did that using pipes.
#include <iostream>
#include <cstdio>
#include <windows.h>
void spawn();
void pwn_send(CONST CHAR chBuf[]);
CHAR* pwn_recv(SIZE_T sz);
CHAR* pwn_recv();
HANDLE stdout_write = NULL;
HANDLE stdout_read = NULL;
HANDLE stdin_write = NULL;
HANDLE stdin_read = NULL;
int main() {
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if(!CreatePipe(&stdout_read, &stdout_write, &saAttr, 0)) return -1;
if(!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) return -1;
if(!CreatePipe(&stdin_read, &stdin_write, &saAttr, 0)) return -1;
if(!SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) return -1;
spawn();
CHAR chBuf[] = "mkdir C:\\Users\\comma\\Desktop\\x\r\n";
pwn_send(chBuf);
std::cout << pwn_recv(20) << std::endl;
return 0;
}
void spawn() {
TCHAR cmd[] = TEXT("cmd.exe");
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.hStdError = stdout_write;
si.hStdOutput = stdout_write;
si.hStdInput = stdin_read;
si.dwFlags |= STARTF_USESTDHANDLES;
ZeroMemory(&pi, sizeof(pi));
if(!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) exit(-1);
return;
}
void pwn_send(CONST CHAR chBuf[]) {
DWORD dwRead = 0, dwWritten = 0;
BOOL bSuccess = FALSE;
while(1) {
bSuccess = WriteFile(stdin_write, chBuf, strlen(chBuf), &dwWritten, NULL);
if (bSuccess) break;
}
CloseHandle(stdin_write);
return;
}
CHAR* pwn_recv(SIZE_T sz) {
HANDLE hHeap = GetProcessHeap();
DWORD dwRead = 0, dwWritten = 0;
BOOL bSuccess = FALSE;
CHAR* chBuf = (CHAR*)HeapAlloc(hHeap, 0, sz+1);
while (1) {
std::cout << "XXX" << std::endl;
bSuccess = ReadFile(stdout_read, chBuf, sz, &dwRead, NULL);
std::cout << "YYY" << std::endl;
if (bSuccess) break;
}
return chBuf;
}
CHAR* pwn_recv() {
HANDLE hHeap = GetProcessHeap();
DWORD dwRead = 0, dwWritten = 0;
BOOL bSuccess = FALSE;
CHAR* chBuf = (CHAR*)HeapAlloc(hHeap, 0, 0x1000);
while (1) {
bSuccess = ReadFile(stdout_read, chBuf, 0x1000, &dwWritten, NULL);
if (bSuccess) break;
}
return chBuf;
}
So the first issue here is that the program I started with spawn(cmd.exe) doesn't properly receive the command I send to stdin. No folder is created anywhere. CreateProcess succeeds. Receiving output doesn't work either, because ReadFile seems to stuck. XXX is displayed in the console, butYYY never. Any ideas?

Win32 communicating with MariaDb

Greetings StackOverflow comrades. Last time I inquired about environment variables. Thanks to Remy for informing me.
Thanks to him I completed my Process class. Now the real problem was connecting to and communicating with MariaDb. I successfully launched MariaDb; but for some reason, reading from MariaDb deadlocks my program. I know before hand that, once connected to MariaDb using, mysql --user=root, MariaDb writes MariaDb[NONE]> to the console. And expects an SQL query input. But I my application deadlocks when trying to read.
I am wondering if MariaDb is using the handles I passed it in CreateProcess StartUpInfo. I did some search on google and found a library on MariaDb website which allows C/C++ programs to connect to MariaDb. So probably they are coercing us to use there library to connect to MariaDb.
Edit:
#Secumen I am trying to communicate with MariaDb via win32 CreateProcess; you know that popular database program? I am using the one shipped with Xampp software.
I want to be able to automate the tasks of adding tables, data, users, etc.
I created the pipes with CreatePipe(...). Then I launched MariaDb using CreateProcess(...). The second argument to CreateProcess was the command line, mysql --user=root. Note that Xampp calls MariaDb MySql. Now I am connected to MariaDb and expect it to write MariaDb[NONE]> to the console. Which means that I should have data to read via ReadFile(...). However ReadFile deadlocks and PeekNamedFile shows that there was zero bytes available to be read.
How the heck then would I communicate with MariaDb if it is not writing to the handles I passed it in CreateProcess?
Edit - Minimal Example
SECURITY_ATTRIBUTES sa = {};
sa.bInheritHandle = true;
sa.lpSecurityDescriptor =NULL;
sa.nLength = sizeof(sa);
HANDLE r,w;
HANDLE r1,w1;
if(!CreatePipe(&r,&w,&sa,0)) throw "Failed to create pipe\n";
if(!CreatePipe(&r1,&w1,&sa,0)) throw "Failed to create pipe\n";
auto cmd = "MYSQL --user=root";
auto current_dir = R"(C:\Program Files\xampp\mysql\bin)";
SetCurrentDirectoryA(current_dir);
STARTUPINFOA si = {sizeof(si)};
PROCESS_INFORMATION pi;
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdError = w;
si.hStdOutput = w;
si.hStdInput = r1;
if(!CreateProcessA(NULL,cmd,NULL,NULL,true,0,NULL,NULL,&si,&pi))
throw "Failed to create process";
CloseHandle(w);
CloseHandle(r1);
{
DWORD sz, avail;
char *buf = new char[1024];
PeekNamedPipe(r,NULL,0,NULL,&avail,NULL);
printf("available %i",avail);
ReadFile(r,buf,1023,&sz,NULL);
buf[sz] = 0;
printf("%s",buf);
delete[] buf;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
I have written the following code by referring to MSDN. I am using visual studio 2017 and test with win32 application.
I have passed several SQL statements through PIPE for testing, and confirmed that the results were exactly obtained through PIPE.
#include <string>
#include <iostream>
#include <windows.h>
using namespace std;
HANDLE hChildOutRd = NULL;
HANDLE hChildOutWr = NULL;
HANDLE hChildInRd = NULL;
HANDLE hChildInWr = NULL;
//. Internal functions.
int CreatePipes();
int CreateChildProcess();
int PipeIO(string & request, string & response);
int main()
{
if (CreatePipes() != ERROR_SUCCESS)
{
cout << "Failed to create pipe. error: " << GetLastError() << endl;
return -1;
}
//. Create the child process.
if (CreateChildProcess() != ERROR_SUCCESS)
{
cout << "Failed to create child process. error: " << GetLastError() << endl;
return -2;
}
//. Write and Read.
string request, response;
request = "use test_db; select count(*) from test_table;";
PipeIO(request, response);
cout << "[Request]: " << request << "\n[Response]: \n" << response << endl << endl;
return 0;
}
int CreatePipes()
{
SECURITY_ATTRIBUTES sa{ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
//. Create a pipe for the child process's output.
if (!CreatePipe(&hChildOutRd, &hChildOutWr, &sa, 0))
{
return -1;
}
if (!SetHandleInformation(hChildOutRd, HANDLE_FLAG_INHERIT, 0))
{
return -2;
}
//. Create a pipe for the child process's input.
if (!CreatePipe(&hChildInRd, &hChildInWr, &sa, 0))
{
return -3;
}
if (!SetHandleInformation(hChildInWr, HANDLE_FLAG_INHERIT, 0))
{
return -4;
}
return ERROR_SUCCESS;
}
int CreateChildProcess()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFO);
si.hStdError = hChildOutWr;
si.hStdOutput = hChildOutWr;
si.hStdInput = hChildInRd;
si.dwFlags |= STARTF_USESTDHANDLES;
wchar_t cmd[] = L" -uroot -ppassword";
BOOL bRet = CreateProcess(L"C:\\xampp\\mysql\\bin\\mysql.exe", cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
if (!bRet)
{
return -5;
}
else
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hChildInRd);
CloseHandle(hChildOutWr);
}
return ERROR_SUCCESS;
}
int PipeIO(string & request, string & response)
{
int nRet = ERROR_SUCCESS;
DWORD dwRead = 0, dwWrite = 0;
response.clear();
if (!WriteFile(hChildInWr, request.c_str(), request.length(), &dwWrite, NULL))
{
cout << "ERROR: failed to write pipe. error: " << GetLastError() << endl;
return -1;
}
CloseHandle(hChildInWr);
while (true)
{
char buffer[1024] = { 0 };
if (!ReadFile(hChildOutRd, buffer, 1024, &dwRead, NULL) || dwRead == 0)
{
break;
}
response += buffer;
}
CloseHandle(hChildOutRd);
return ERROR_SUCCESS;
}
Then, you can do this asynchronously.
I referred to RbMm's answer at this article.
#include <malloc.h>
#include <windows.h>
#include <winternl.h>
#include <array>
#include <string>
#include <iostream>
typedef ULONG(__stdcall *RTLNTSTATUSTODOSERROR)(NTSTATUS);
RTLNTSTATUSTODOSERROR pRtlNtStatusToDosError = NULL;
struct IO_COUNT
{
HANDLE _hFile;
HANDLE _hEvent;
LONG _dwIoCount;
IO_COUNT()
{
_dwIoCount = 1;
_hEvent = 0;
}
~IO_COUNT()
{
if (_hEvent)
{
CloseHandle(_hEvent);
}
}
void BeginIo()
{
InterlockedIncrement(&_dwIoCount);
}
void EndIo()
{
if (!InterlockedDecrement(&_dwIoCount))
{
SetEvent(_hEvent);
}
}
void Wait()
{
WaitForSingleObject(_hEvent, INFINITE);
}
ULONG Create(HANDLE hFile);
};
struct U_IRP : OVERLAPPED
{
enum { read, write };
IO_COUNT* _pIoObject;
ULONG _code;
LONG _dwRef;
char _buffer[256];
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
U_IRP(IO_COUNT* pIoObject) : _pIoObject(pIoObject)
{
_dwRef = 1;
pIoObject->BeginIo();
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
}
~U_IRP()
{
_pIoObject->EndIo();
}
ULONG CheckIoResult(BOOL is_ok)
{
if (is_ok)
{
OnIoComplete(NOERROR, InternalHigh);
return NOERROR;
}
ULONG dwErrorCode = GetLastError();
if (dwErrorCode != ERROR_IO_PENDING)
{
OnIoComplete(dwErrorCode, 0);
}
return dwErrorCode;
}
ULONG Read()
{
_code = read;
AddRef();
return CheckIoResult(ReadFile(_pIoObject->_hFile, _buffer, sizeof(_buffer) - 1, 0, this));
}
ULONG Write(const void* pvBuffer, ULONG cbBuffer)
{
_code = write;
AddRef();
return CheckIoResult(WriteFile(_pIoObject->_hFile, pvBuffer, cbBuffer, 0, this));
}
VOID OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered)
{
switch (_code)
{
case read:
if (dwErrorCode == NOERROR)
{
if (dwNumberOfBytesTransfered)
{
_buffer[dwNumberOfBytesTransfered] = 0;
std::cout << _buffer;
}
Read();
}
break;
case write:
break;
}
Release();
}
static VOID WINAPI _OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{
static_cast<U_IRP*>(lpOverlapped)->OnIoComplete(pRtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
};
ULONG IO_COUNT::Create(HANDLE hFile)
{
_hFile = hFile;
return BindIoCompletionCallback(hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)U_IRP::_OnIoComplete, 0) &&
SetFileCompletionNotificationModes(hFile, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) &&
(_hEvent = CreateEvent(0, TRUE, FALSE, 0)) ? NOERROR : GetLastError();
}
int main()
{
static const WCHAR name[] = L"\\\\?\\pipe\\somename";
pRtlNtStatusToDosError = (RTLNTSTATUSTODOSERROR)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlNtStatusToDosError");
HANDLE hFile = CreateNamedPipeW(name, PIPE_ACCESS_DUPLEX | FILE_READ_DATA | FILE_WRITE_DATA | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 0, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return -1;
}
IO_COUNT obj;
if (obj.Create(hFile) != NOERROR)
{
CloseHandle(hFile);
return -2;
}
PROCESS_INFORMATION pi;
STARTUPINFOW si = { sizeof(si) };
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = CreateFile(name, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0);
if (si.hStdError == INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return -3;
}
si.hStdInput = si.hStdOutput = si.hStdError;
WCHAR param[] = L" -uroot -ppassword";
if (!CreateProcess(L"C:\\xampp\\mysql\\bin\\mysql.exe", param, 0, 0, TRUE, 0, 0, 0, &si, &pi))
{
CloseHandle(hFile);
return -4;
}
//. Close unneeded handles.
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(si.hStdError);
U_IRP* p;
if (p = new U_IRP(&obj))
{
p->Read();
p->Release();
}
obj.EndIo();
std::array<std::string, 5> commands = {
"show databases;\n",
"use test_db;\n",
"select count(*) from test_table;\n",
"select * from test_table;\n",
"exit\n"
};
for (auto & iter : commands)
{
if (p = new U_IRP(&obj))
{
p->Write(iter.c_str(), iter.length());
p->Release();
}
}
obj.Wait();
CloseHandle(hFile);
DisconnectNamedPipe(hFile);
return 0;
}

c++ Pipe Redirection with CreateProcess() issues

I'm attempting to make a remote administrator tool, so I can control my home computer, and I have the server working. I can send commands across the network fine, but I'm having trouble executing them in the cmd. I have tried to use the CreateProcess() function to start the cmd and then write commands through a pipe, and read the result. I would like to do this multiple times without closing the cmd, so that I can use cd, etc.
It seems like it is at least partially working because it prints out the welcome message for the cmd when the startCmd() function is called. After this however, when I try to write commands to the cmd it never gives me any output. When I check the out pipe, it says that it has read 0 bytes, except when it first starts.
Does this mean that I can only execute 1 command, or do I need to manipulate the pipes in some way after using them once, or something else like that? Also, I apologize if the code is sloppy, I have just been trying a bunch of different solutions and I havent been worrying about the cleanliness of my code.
#define BUFSIZE 4096
#define PATHMAX 400
bool running = false;
HANDLE hChildStdInR = NULL;
HANDLE hChildStdInW = NULL;
HANDLE hChildStdOutR = NULL;
HANDLE hChildStdOutW = NULL;
PROCESS_INFORMATION piProcInfo;
void ErrorExit(const char*);
bool startCmd()
{
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&hChildStdOutR, &hChildStdOutW, &saAttr, 0))
ErrorExit("StdoutRd CreatePipe");
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(hChildStdOutR, HANDLE_FLAG_INHERIT, 0))
ErrorExit("Stdout SetHandleInformation");
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&hChildStdInR, &hChildStdInW, &saAttr, 0))
ErrorExit("Stdin CreatePipe");
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(hChildStdInW, HANDLE_FLAG_INHERIT, 0))
ErrorExit("Stdin SetHandleInformation");
char cmdPath[PATHMAX];
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// 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.hStdError = hChildStdOutW;
siStartInfo.hStdOutput = hChildStdOutW;
siStartInfo.hStdInput = hChildStdInR;
siStartInfo.wShowWindow = SW_HIDE;
siStartInfo.dwFlags |= STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
GetEnvironmentVariableA("ComSpec", cmdPath, sizeof(cmdPath));
// Create the child process.
bSuccess = CreateProcess(
cmdPath,
NULL, // command line (NULL because application itsself is cmd)
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
CREATE_NEW_CONSOLE, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// Close un-needed pipes
/*CloseHandle(hChildStdOutW);
CloseHandle(hChildStdInR);*/ // Doesn't change anything why I uncomment these lines
// If an error occurs, exit the application.
if (!bSuccess)
ErrorExit("CreateProcess");
else
{
// Close handles to the child process and its primary thread. Some applications might keep these handles to monitor the status of the child process, for example.
/*CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);*/
}
return true;
}
bool writeToCmd(const string& s)
{
DWORD dwWritten;
const char* cmd = s.c_str();
return WriteFile(hChildStdInW, cmd, sizeof(cmd), &dwWritten, NULL);
}
bool exec(const string& command)
{
if (!writeToCmd(command)) {
return false;
}
else {
cout << "Succesfully Wrote" << endl;
}
return true;
}
void checkPipe()
{
while (running) {
while (1) {
Sleep(50);
DWORD bytesAvail = 0;
if (!PeekNamedPipe(hChildStdOutR, NULL, 0, NULL, &bytesAvail, NULL)) {
cout << "Failed to call PeekNamedPipe" << endl;
}
if (bytesAvail) {
CHAR buf[BUFSIZE];
DWORD n;
BOOL success = ReadFile(hChildStdOutR, buf, BUFSIZE, &n, NULL);
if (!success || n == 0) {
cout << "Failed to call ReadFile" << endl;
break;
}
string s = string(buf, buf + n);
cout << s << endl;
break;
}
}
}
}
int main(int argc, char** argv)
{
if (argc != 2) {
cout << "Usage: " << argv[0] << " <ADRESS>" << endl;
return 1;
}
ClientSocket client(argv[1], DEFAULT_PORT);
// Wait for initial response
string w = client.recieveLine();
if (w == "welcome") {
cout << "Connection Successful! " << endl;
}
running = true;
if (startCmd()) cout << "Cmd Started" << endl;
thread checkLoop(&checkPipe);
while (true) {
vector<string> command = split(client.recieveLine());
if (command[0] == "run") {
exec(command[1]);
}
else if (command[0] == "exit") {
running = false;
client.sendLine("exit");
break;
}
}
if (!CloseHandle(hChildStdInW))
ErrorExit("StdInWr CloseHandle");
checkLoop.join();
client.close();
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
return 0;
}

Printing from a process spawned from a service on Windows 2008 server

The short version:
I have written a small program to print a report. This program is exec'ed from our proprietary server, that is running as a windows service. It works in our development environment, but not on our customer's network.
When I attempt to access the printing system in the failing case, I get a "No default printer" error. We had the customer create a new login account that has a default printer defined, and restarted the server using that login account. Same error.
Note that this error is generated when trying to find a specified printer, not when we try to print to it.
Is there any way to convince a server-spawned process that printers do, in fact, exist?
The long version:
In our "current" production environment, we have the following:
1. a proprietary server that runs as a service under windows.
2. a desktop client
--> accesses data via that service
--> uses fastreport4 to generate reports
--> developed using C++Builder6 (and VCL)
3. a PocketPC-based application that runs on scanning devices
--> uses Apache to communicate with the service
--> also uses Apache to poke a cgi-bin application that will bring up
the desktop app in stealth mode, run a report, and print it.
I have been tasked with re-implementing the Pocket-PC functionality on an Android-based scanning device, and removing the Apache layer from the architecture. In the Android app, I've written a communication layer to access the server (service) directly (the same way the desktop does). The server has the ability to exec applications, so I've written a small one to just gather data, and call fastreport to format and print it.
Works great. No problem. ... in our development environment. It works in our office network with the server running on a Windows 7 system. It works when run from the command line on our customer's Windows 2008 server. It does not work when run from the service on the customer's server.
So here's the code. In my current revision, I have try/catch and debug print statements around (nearly) every line of code. I removed them for readability.
bool __fastcall TFormReportRunner::mySetPrinter(const char* name)
{
char pDevice[MAX_PATH];
char pDriver[MAX_PATH];
char APort[100];
UINT ADeviceMode;
bool printerFound = false;
bool errorFound = false;
String PrinterPort = String(name).UpperCase();
TPrinter* Prntr;
// I added this bit to see if it helps. Seems to make no difference
bool rc = SetDefaultPrinter("");
Prntr = Printer();
if (Prntr == NULL)
{
LogErrorMsg("Printer() returned null.");
return false;
}
int i = Prntr->Printers->Count - 1;
for (; i >= 0; i--)
{
// In the failing case, this next statement is the one that causes an exception.
Prntr->PrinterIndex = i;
Prntr->GetPrinter(pDevice, pDriver, APort, ADeviceMode);
DWORD SizeNeeded = 0;
HANDLE PrinterHandle;
if (OpenPrinter(pDevice, &PrinterHandle, NULL) == 0)
{
LogErrorMsg("Could not open printer");
return false;
}
GetPrinter(PrinterHandle, 2, NULL, 0, &SizeNeeded);
if (SizeNeeded == 0)
{
ClosePrinter(PrinterHandle);
LogErrorMsg("Could not retrieve printer info size");
return false;
}
PRINTER_INFO_2 PrinterInfo2;
char* buffer = new char[SizeNeeded];
if (GetPrinter(PrinterHandle, 2, buffer, SizeNeeded, &SizeNeeded) == 0)
{
ClosePrinter(PrinterHandle);
delete [] buffer;
LogErrorMsg("Could not retrieve printer info");
return false;
}
String PortName = ((PRINTER_INFO_2*)buffer)->pPortName;
delete [] buffer;
ClosePrinter(PrinterHandle);
if (PrinterPort == PortName)
{
frxReport1->PrintOptions->Printer = pDevice;
break;
}
}
Prntr->PrinterIndex = i;
return true;
}
One of the customer's IT guys says that in order to have the Apache version work they have to run Apache as administrator with a defined default printer AND have that admin account logged in for printing to work. He suspects that if we run our service in the same configuration it will start to work. I have not been able to replicate that on our network. My admin account always works, whether anyone is currently logged into the system, or not. But this is Windows 7/Professional, and not a server version.
This has got to be possible... Apache's doing it. If it can find a printer and print to it, I should be able to, right?
Any hints or help will be greatly appreciated. Anything. Really. :)
Thanks,
-Karen
Edit: Adding server-side code.
A couple of notes, first. One, this was written 20-ish years ago (by someone else). Two, it is NOT a VCL app (and was compiled with the Microsoft compiler). Three, we will not change it. Recompiling with current compilers is WAY too risky for something that is otherwise working.
int myServer::RunProcess2(ClientCall *call, vector<string> &args, vector<string> &env, const char* input, unsigned int insize,
string *output, string *error)
{
CancelProcess2(); // only one process allowed per connection
string cmdline;
for (unsigned int i = 0; i < args.size(); i++)
{
if (i != 0)
cmdline += ' ';
cmdline += args[i];
}
env.push_back(EnvPATH);
int size = 1;
for (unsigned int i = 0; i < env.size(); i++)
{
size += env[i].size() + 1;
}
char *environment = (char*)malloc(size);
if (environment == NULL)
{
call->error = "Could not allocate memory for process environment variables";
return 0;
}
char *ptr = environment;
for (unsigned int i = 0; i < env.size(); i++)
{
size = env[i].size() + 1;
memcpy(ptr, env[i].c_str(), size);
ptr += size;
}
ptr[0] = '\0';
HANDLE hInputReadPipe = NULL, hInputWritePipe = NULL;
HANDLE hReadPipe, hWritePipe;
HANDLE hErrorReadPipe, hErrorWritePipe;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = true;
// create output pipe
if (CreatePipe(&hReadPipe, &hWritePipe, &sa, 4096) == 0)
{
free(environment);
call->error = "Error creation Pipe";
return 0;
}
// create error pipe
if (CreatePipe(&hErrorReadPipe, &hErrorWritePipe, &sa, 4096) == 0)
{
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
free(environment);
call->error = "Error creating Pipe";
return 0;
}
if (insize > 0)
{
// create input pipe
if (CreatePipe(&hInputReadPipe, &hInputWritePipe, &sa, 4096) == 0)
{
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
CloseHandle(hErrorReadPipe);
CloseHandle(hErrorWritePipe);
free(environment);
call->error = "Error creating Pipe";
return 0;
}
}
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdOutput = hWritePipe;
si.hStdError = hErrorWritePipe;
si.hStdInput = hInputReadPipe;
PROCESS_INFORMATION pi;
if (CreateProcess(NULL, (char*)cmdline.c_str(), NULL, NULL, true, 0, environment, NULL, &si, &pi) == 0)
{
CloseHandle(hErrorReadPipe);
CloseHandle(hErrorWritePipe);
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
if (hInputReadPipe != NULL)
{
CloseHandle(hInputReadPipe);
CloseHandle(hInputWritePipe);
}
free(environment);
call->error = string("Error executing command: ") + cmdline + "\n" + GetErrorText();
return 0;
}
report_handle = pi.hProcess;
CloseHandle(hErrorWritePipe);
CloseHandle(hWritePipe);
if (hErrorReadPipe != NULL)
CloseHandle(hInputReadPipe);
char buffer[4097];
DWORD BytesAvail;
DWORD BytesRead = 0;
DWORD BytesWritten = 0;
DWORD BytesToRead = sizeof(buffer) - 1;
DWORD BytesToWrite = insize;
bool finished_readpipe = false, finished_errorpipe = false;
bool finished_inputpipe = (insize == 0);
int wait_time = 1;
bool error_occurred = false;
while (finished_readpipe == false || finished_errorpipe == false || finished_inputpipe == false)
{
if (finished_inputpipe == false)
{
if (BytesToWrite <= 0)
{
CloseHandle(hInputWritePipe);
hInputWritePipe = NULL;
finished_inputpipe = true;
continue;
}
BytesAvail = 1000;
/*if (PeekNamedPipe(hInputWritePipe, NULL, NULL, NULL, &BytesAvail, NULL) == 0)
{
DWORD temp = GetLastError();
// pipe has been closed
finished_inputpipe = true;
continue;
}*/
if (BytesAvail > 0)
{
if (BytesAvail > BytesToWrite)
BytesAvail = BytesToWrite;
if (WriteFile(hInputWritePipe, input, BytesAvail, &BytesWritten, NULL) == 0)
{
if (GetLastError() == ERROR_NO_DATA)
{
int a = 2; // Pipe was closed (normal exit path).
}
finished_inputpipe = true;
continue;
}
input += BytesWritten;
BytesToWrite -= BytesWritten;
if (BytesToWrite == 0)
{
finished_inputpipe = true;
}
continue;
}
}
if (finished_readpipe == false)
{
while (true)
{
if (PeekNamedPipe(hReadPipe, NULL, NULL, NULL, &BytesAvail, NULL) == 0)
{
// pipe has been closed
finished_readpipe = true;
break;
}
if (BytesAvail <= 0)
break;
if (BytesAvail > sizeof(buffer) - 1)
BytesAvail = sizeof(buffer) - 1;
if (ReadFile(hReadPipe, buffer, BytesAvail, &BytesRead, NULL) == 0)
{
finished_readpipe = true;
break;
}
if (BytesRead == 0)
{
finished_readpipe = true;
break;
}
buffer[BytesRead] = '\0';
*output += buffer;
if (output->length() >= MAX_PROCESS_OUTPUT)
{
finished_inputpipe = true;
finished_readpipe = true;
finished_errorpipe = true;
error_occurred = true;
call->error = "Output limit reached";
}
}
if (finished_readpipe == true)
continue;
}
if (finished_errorpipe == false)
{
while (true)
{
if (PeekNamedPipe(hErrorReadPipe, NULL, NULL, NULL, &BytesAvail, NULL) == 0)
{
// pipe has been closed
finished_errorpipe = true;
break;
}
if (BytesAvail <= 0)
break;
if (BytesAvail > sizeof(buffer) - 1)
BytesAvail = sizeof(buffer) - 1;
if (ReadFile(hErrorReadPipe, buffer, BytesAvail, &BytesRead, NULL) == 0)
{
finished_errorpipe = true;
break;
}
if (BytesRead == 0)
{
finished_errorpipe = true;
break;
}
buffer[BytesRead] = '\0';
*error += buffer;
if (error->length() >= MAX_PROCESS_OUTPUT)
{
finished_inputpipe = true;
finished_readpipe = true;
finished_errorpipe = true;
error_occurred = true;
call->error = "Error output limit reached";
}
}
if (finished_errorpipe == true)
continue;
}
// don't tie up the server
if (wait_time < 100)
wait_time++;
Sleep(wait_time);
}
if (error_occurred == false)
WaitForSingleObject(pi.hProcess, INFINITE);
process_mutex.lock();
report_handle = NULL;
process_mutex.unlock();
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(hReadPipe);
CloseHandle(hErrorReadPipe);
if (hInputWritePipe != NULL)
CloseHandle(hInputWritePipe);
free(environment);
return exit_code;
}

C ++ Inter Process Communication using redirected Input and Output not work with release build

I'm working on a project which needed to create a child process and re-directed its input and output to the parent process.
I'm following this example on MSDN (link). Currently it works with debug build, but not release build. I can't figure it out why. Please help me
The coding of the child process as follow (very similar to the example):
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string>
#include <Iepmapi.h>
#include <Wininet.h>
#include <vector>
#define BUFSIZE 4096
using namespace std;
vector<wstring> subStringByString(wstring input, wstring delimiter)
{
int pos = input.find(delimiter);
vector<wstring> arr;
while (pos != wstring::npos)
{
wstring token = input.substr(0, pos);
arr.push_back(token);
input = input.substr(pos + delimiter.size(), input.size());
pos = input.find(delimiter);
}
arr.push_back(input);
return arr;
}
int main(void)
{
WCHAR chBuf[BUFSIZE];
DWORD dwRead, dwWritten;
HANDLE hStdin, hStdout;
BOOL bSuccess;
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (
(hStdout == INVALID_HANDLE_VALUE) ||
(hStdin == INVALID_HANDLE_VALUE)
)
ExitProcess(1);
// Send something to this process's stdout using printf.
printf("\n ** This is a message from the child process. ** \n");
// This simple algorithm uses the existence of the pipes to control execution.
// It relies on the pipe buffers to ensure that no data is lost.
// Larger applications would use more advanced process control.
int rc = 0;
for (;;)
{
// Read from standard input and stop on error or no data.
bSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL);
wstring input = wstring(chBuf);
wstring delimiter = L" ";
vector<wstring> arr = subStringByString(input, delimiter);
int rc = 0;
if (!bSuccess || dwRead == 0)
break;
if (IESetProtectedModeCookie(arr[0].c_str(), arr[1].c_str(), arr[2].c_str(), INTERNET_COOKIE_THIRD_PARTY) != S_OK)
{
DWORD error = GetLastError();
rc = -27;
}
// Write to standard output and stop on error.
if (rc == 0 )
bSuccess = WriteFile(hStdout, L"0", dwRead, &dwWritten, NULL);
else
bSuccess = WriteFile(hStdout, L"-27", dwRead, &dwWritten, NULL);
if (!bSuccess)
break;
}
return 0;
}
Thanks,
Vinh
Parent process code:
std::wstring invoke(const std::wstring input)
{
if (!CreateChildProcess())
return L"error";
DWORD dwRead, dwWritten;
WCHAR chBuf[BUFSIZE] ;
const WCHAR* temp = input.c_str();
wcscpy(chBuf, temp);
BOOL bSuccess = FALSE;
for (;;)
{
bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess) break;
}
CloseHandle(g_hChildStd_IN_Wr);
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
for (;;)
{
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess) break;
bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess) break;
}
//TerminateProcess(_processId, 0);
return std::wstring(&chBuf[0]);
}
bool CreateChildProcess(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 STDIN.
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
return false;
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
{
cleanUpHandle();
return false;
}
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
{
cleanUpHandle();
return false;
}
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
{
cleanUpHandle();
return false;
}
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// 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.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
HANDLE hToken = NULL;
HANDLE hNewToken = NULL;
bSuccess = OpenProcessToken(GetCurrentProcess(),
TOKEN_DUPLICATE |
TOKEN_ADJUST_DEFAULT |
TOKEN_QUERY |
TOKEN_ASSIGN_PRIMARY,
&hToken);
if (!bSuccess) {
return 0;
}
hNewToken = CreateLowLevelToken(hToken);
wa_wstring deploymentPath;
if (WAAPI_FAILED(ProcessUtils::getDeploymentPath(deploymentPath)))
{
return false;
}
wa_wstring path = deploymentPath + wa_wstring(PROCESS_PATH);
LPTSTR szCmdline = wstring_to_LPTSTR(path);
if (_processId != 0)
{
TerminateProcess(_processId, 0);
}
TCHAR szCmdline2[] = PROCESS_PATH;
bSuccess = CreateProcessAsUser(hNewToken, NULL, szCmdline2, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &siStartInfo, &piProcInfo);
DWORD error = GetLastError();
if (!bSuccess)
{
cleanUpHandle();
return false;
}
else {
_processId = piProcInfo.dwProcessId;
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
}
return true;
}
I found the problem.
In "debug build, the value of dwRead, dwWritten are trash value, however, the value of those in relase build are 0. Which cause the parent hang.
To fix this :
DWORD dwRead = -1, dwWritten = -1;