File re-direction operator ">" doesn't work with CreateProcess() API - c++

I've a SupportApp.EXE which if I launch manually from windows CMD prommpt like this ::
SupportApp.EXE -t 100 > AFile.csv
works perfcetly fine & it generates a CSV file for me.
Now I want to automate same thing inside a VC++ code.
So, I use CreateProcess() API for this.
Code snippet below ::
TCHAR launcher[512];
_tgetcwd(launcher, _MAX_PATH);
TCHAR workDir[512];
_tgetcwd(workDir, _MAX_PATH);
_tcscat(launcher, "\\App\\SupportApp.exe");
TCHAR cmdlineoption[512];
_tcscpy(cmdlineoption, " -t 120 > AFile.csv");
LPTSTR appPath = (LPTSTR)cmdlineoption;
STARTUPINFO sInfo;
memset(&sInfo, 0, sizeof(sInfo));
sInfo.cb = sizeof(sInfo);
sInfo.dwFlags = STARTF_USESHOWWINDOW;
sInfo.wShowWindow = SW_SHOWMAXIMIZED;
PROCESS_INFORMATION pInfo;
memset(&pInfo, 0, sizeof(pInfo));
if (!CreateProcess(launcher, appPath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, workDir, &sInfo, &pInfo))
{
... // log error
}
// success
I see that CreateProcess() API succeeds and also i see that the -t 120 option I'm giving is also taken by this "SupportApp.exe"
BUT the file redirection operator ">" is not working with CreateProcess() API.
Instead the output is directed to CMD itself. But I want output to be sent to a CSV file.
Can anyone please help me in how do I redirect the output of my
"SupportApp.exe" to a file using CreateProcess() API from within my
VC++ code ?
UPDATE 2:
The inputs given by reviewers are incorporated in this & the modified code snippet is below which takes the file hnadle in STARTUPINFO structure as follows::
The file is getting created but the file is empty & it doesn't have any contents from createProcess()?
TCHAR launcher[512];
_tgetcwd(launcher, _MAX_PATH);
TCHAR workDir[512];
_tgetcwd(workDir, _MAX_PATH);
_tcscat(launcher, "\\App\\SupportApp.exe");
TCHAR cmdlineoption[512];
_tcscpy(cmdlineoption, " -t 120 > AFile.csv");
LPTSTR appPath = (LPTSTR)cmdlineoption;
STARTUPINFO sInfo;
memset(&sInfo, 0, sizeof(sInfo));
sInfo.cb = sizeof(sInfo);
sInfo.dwFlags |= STARTF_USESTDHANDLES; //newly added
sInfo.wShowWindow = SW_SHOWMAXIMIZED;
PROCESS_INFORMATION pInfo;
memset(&pInfo, 0, sizeof(pInfo));
sInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
sInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(sa));
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
HANDLE hn;
if(INVALID_HANDLE_VALUE != (hn = CreateFile(L"DoneDone.csv", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)))
{
sInfo.hStdOutput = =hn;
}
if (!CreateProcess(launcher, appPath, NULL, NULL, FALSE, 0, NULL, workDir, &sInfo, &pInfo))
{
... // log error
}
// success

Output redirection is a shell feature, i.e. the shell sets that up before starting the child.
You're not using a shell, instead going directly to the kernel asking it to start a process, so you don't get that service.
You need to set up the required redirection yourself. This is done in the STARTUPINFO's hStdOutput member. See the documentation, of course.

That's because the redirection operations (as well as the pipe operation) is part of the command prompt program, not part of the CreateProcess call.
However, you can do exactly what the command prompt program does when it does redirection, and set the file handles in the STARTUPINFO structure.

Related

Redirecting CreateProcess input stream to a file

I'm using CreateProcess to substitute a system() call in my code. I was using:
system(xfoil.exe < create_results.txt");
Which I am substituing by this:
PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter
STARTUPINFO StartupInfo; //This is an [in] parameter
LPCWSTR input_file = _tcsdup(TEXT(".\\create_results.txt"));
HANDLE inpfl = CreateFile(input_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
StartupInfo.hStdInput = inpfl;
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof StartupInfo; //Only compulsory field
LPCWSTR exe_path =_tcsdup(TEXT(".\\xfoil.exe"));
if (CreateProcess(exe_path, NULL,
NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL,
NULL, &StartupInfo, &ProcessInfo))
{
WaitForSingleObject(&ProcessInfo.hProcess, 2000);
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(inpfl);
}
else
{
CloseHandle(inpfl);
std::cout << "Could not create xfoil process" << std::endl;
}
The reason being I need to control how long the process is allowed to run ( 2000ms in this case), however it seems that this method does not work.
I'm redirecting the input of the process to the handle of the file I want as input (to substitute the < operator), but the process is not receiving anything. It does launch the xfoil.exe in a separate console, though.
You need to set file security attributes to allow handle to be inherited and startup info flag to make child process use the handle passed:
::STARTUPINFO startup_information{};
::SECURITY_ATTRIBUTES security_attributes
{
sizeof(::SECURITY_ATTRIBUTES)
, nullptr
, TRUE // allow handle to be inherited
};
::HANDLE const inpfl{::CreateFileW(input_file, GENERIC_READ, FILE_SHARE_READ, ::std::addressof(security_attributes), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)};
if(INVALID_HANDLE_VALUE == inpfl)
{
auto const last_error{::GetLastError()};
// handle error...
}
startup_information.cb = sizeof(startup_information);
startup_information.dwFlags = STARTF_USESTDHANDLES;
startup_information.hStdInput = inpfl;
PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter
STARTUPINFO StartupInfo; //This is an [in] parameter
LPCWSTR input_file = _tcsdup(TEXT(".\\create_results.txt"));
HANDLE inpfl = CreateFile(input_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
StartupInfo.hStdInput = inpfl;
What happens to StartupInfo.hStdInput after the next call?
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
Oops... hStdInput zeroed now
Doing this initially...
PROCESS_INFORMATION ProcessInfo = {};
STARTUPINFO StartupInfo = {};
... will prevent you from needing to zero anything... see
Also, according to CreateProcess StartupInfo documentation, you have to, for hStdInput, set dwFlags to STARTF_USESTDHANDLES, else the default input is the keyboard buffer

How to use CreateProcess or ShellExecute to execute piped commands

I want to execute a few commands piped without new cmd opened (so I can't just use system())
This is the command I try to execute:
C:\\openssl.exe enc -aes-128-ofb -d -in C:\\encrypted.bin -iv a2b050be9463 -K 6ba62eb7bb2ccace -nopad | C:\\\\mplayer.exe -"
This is what I tried :
WCHAR prog[] = L"C:\\openssl.exe";
WCHAR args[] = L"enc -aes-128-ofb -d -in C:\\encrypted.bin -iv a2b050be9463 -K 6ba62eb7bb2ccace -nopad | C:\\mplayer.exe -";
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(prog, args, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
And it didn't work (There is no error, its just not opening)
I also tried this:
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
HINSTANCE hinstRun1 = ShellExecute(NULL, L"open", L"cmd.exe", str2.c_str(), L"", SW_HIDE);
CoUninitialize();
//str2 == C:\\openssl.exe enc -aes-128-ofb -d -in C:\\encrypted.bin -iv a2b050be9463 -K 6ba62eb7bb2ccace -nopad | C:\\\\mplayer.exe -")
This is also not working (Again there is no error, its just not opening)
When I tried it like this :
system(("cmd.exe /c " str2).c_str());
Everything works good (Except the part that its opened also a cmd window.)
How can I execute this line from c/c++ program without new cmd window?
If you want to be in control of everything, you need to create both processes (openssl and mplayer) yourself. So that would be two CreateProcess calls. Of course, then, you have to create the redirection yourself, as well, and this is done using CreatePipe, before creating the processes.
Here is an example (haven't compiled it, may require some tuning):
HANDLE proc1_out;
HANDLE proc2_in;
SECURITY_ATTRIBUTES security_attributes;
// create the pipe between the processes
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = TRUE; // pipe handles should be inheritable by sub-processes
security_attributes.lpSecurityDescriptor = NULL;
CreatePipe(&proc2_in, &proc1_out, &security_attributes, 0);
// create the first process
WCHAR proc1_app[] = L"C:\\openssl.exe";
WCHAR proc1_cmd_line[] = L"openssl enc -aes-128-ofb -d -in C:\\encrypted.bin -iv a2b050be9463 -K 6ba62eb7bb2ccace -nopad";
PROCESS_INFORMATION proc1_info;
STARTUPINFO proc1_startup_info;
ZeroMemory(&proc1_info, sizeof(PROCESS_INFORMATION));
ZeroMemory(&proc1_startup_info, sizeof(STARTUPINFO));
proc1_startup_info.cb = sizeof(STARTUPINFO);
proc1_startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
proc1_startup_info.hStdOutput = child_output_write; // redirected output
proc1_startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
proc1_startup_info.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess(proc1_app, proc1_cmd_line, NULL, NULL, TRUE, 0, NULL, NULL, &proc1_startup_info, &proc1_info);
// create the second process
WCHAR proc2_app[] = L"C:\\mplayer.exe";
WCHAR proc2_cmd_line[] = L"mplayer -";
PROCESS_INFORMATION proc2_info;
STARTUPINFO proc2_startup_info;
ZeroMemory(&proc2_info, sizeof(PROCESS_INFORMATION));
ZeroMemory(&proc2_startup_info, sizeof(STARTUPINFO));
proc2_startup_info.cb = sizeof(STARTUPINFO);
proc2_startup_info.hStdInput = proc2_in; // redirected input
proc2_startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
proc2_startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
proc2_startup_info.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess(proc2_app, proc2_cmd_line, NULL, NULL, TRUE, 0, NULL, NULL, &proc2_startup_info, &proc2_info);
Notes: in the command line argument passed to CreateProcess, the first "word" in the string must be the process name (this was not what you were doing).

grep: input file ">":System cannot find the file specified

when I tried to execute a grep command in c++, I got the following error:
grep: Input file: ">":System cannot find the file specified
Can anyone help me to resolve this?
wchar_t* grepArg= L"\"D:\\grep\" -f \"C:\\Users\\ic014733\\AppData\\Local\\Temp\\patterns.tmp\" \"D:\\LOG2014_10_05.LOG\" >\"D:\\share\\result.txt\"";
if ( !CreateProcessW(NULL, grepArg, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) )
{
DWORD errorMessageID = ::GetLastError();
}
else
{
DWORD res = WaitForSingleObject(pi.hProcess, INFINITE);
TerminateProcess(pi.hProcess, 0);
PostThreadMessage(pi.dwThreadId, WM_QUIT, 0, 0) ;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
You gave the answer in your comment : Error is like, grep considers '>' too as input file. Of course it does! Why should it not?
Let me explain a little: When you write grep -f strings_file file > result at a shell prompt (or cmd prompt under Windows), the shell parses the input line, indentifies the > redirection and processes the redirection:
open result file
start program grep with arguments (or command line for Windows) -f strings_file file and standard output redirected to result
But you are just starting the grep program with all the arguments in its command line, so no redirection can happen.
So what can you do? As none of your path contains spaces, you could just make cmd.exe do the redirection for you :
wchar_t* grepArg= L"cmd.exe /C \"D:\\grep -f C:\\Users\\ic014733\\AppData\\Local\\Temp\\patterns.tmp D:\\LOG2014_10_05.LOG > D:\\share\\result.txt";
That means that you start a new cmd.exe program with two parameters: /c saying execute that and your original command enclosed in quotes. But I never found the way to cleanly include quotes in quotes in Windows. That's the reason why I removed all the inner quotes from your command. It was possible because there was no space in any path.
The alternative would be to do the redirection by hand: open the output file, pass its handle in the hStdOutput member of si and declare it by setting STARTF_USESTDHANDLES in dwFlags member of same struct:
wchar_t* grepArg= L"\"D:\\grep\" -f \"C:\\Users\\ic014733\\AppData\\Local\\Temp\\patterns.tmp\" \"D:\\LOG2014_10_05.LOG\"";
HANDLE hstdout = CreateFile("D:\\share\\result.txt", "GENERIC_WRITE", 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// control hstdout is not null...
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hstdout;
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); // stdinput and stderr unchanged
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if ( !CreateProcessW(NULL, grepArg, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) ) {
...

pass filename from existing Process to new Process MFC c++

I'm trying to figure out how to pass filename from within an existing executable to a newly generated executable of same type & then the new exe load said file name. Following is something I'm working on but I'm bit lost really.
CString cstrExePathLoc;
GetModuleFileName(NULL, cstrExePathLoc.GetBuffer(MAX_PATH), MAX_PATH);
wchar_t szCommandLine[1024] = _T("C:\\Users\\Home\\Desktop\\testfile.tmp");
PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo;
::ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
CreateProcess(
cstrExePathLoc, szCommandLine, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
&startupInfo, &processInfo
);
EDIT: this still doesn't open the file. A new ExeApp is started but no file is loaded. No errors generated at all.
I've searched net but no examples I've come across clearly explain how to do this. Any help would be appreciated. Thanks.
EDIT: simple solution here that's worked thanks to Robson Filho Colodeti below.
CString cstrExeFilePathAndFilePath2Open = cstrExePathLoc;
cstrExeFilePathAndFilePath2Open += L" \"";
cstrExeFilePathAndFilePath2Open += cstrFilePath2Open;
cstrExeFilePathAndFilePath2Open += L"\"";
CreateProcess(csExePath, cstrExeFilePathAndFilePath2Open.GetBuffer(0), NULL, NULL, TRUE, NULL, NULL, NULL, &sui, &pi);
Opening the other program
Using CreateProcess
you are in the right way, you can use the CreateProcess method.
BOOL fSuccess;
CString csDir = L"c:\your\working\directory\";
CString csParameters = L"parameter1 parameter2 parameter3 /parameter4=value";
CString csCommand = L"c:\folder\of\the\executable\executable.exe";
csCommand+= L" ";
csCommand+= csParameters;
// Create the child process.
fSuccess = CreateProcess(NULL, csCommand.GetBuffer(0), NULL, NULL, TRUE, 0, NULL,
csDir, &startupInfo, &processInfo);
Using ShellExecute
an easier way is to use the ShellExecute method because the create process method is a more "advanced" way to call a process since it gives you a lot of possibilities to control the results etc...
Reading the parameters inside the other program
then you will have to read these parameters from the other executable: check this thread

How to redirect in, out and err streams from process created with CreateProcess function? [duplicate]

I tried using CreateProcess to run a simple command like hg > test.txt. I tried running the string as a whole (as opposed to separating it into an application name and its parameters). Why does CreateProcess(0, "notepad.exe test.txt", ...) work but CreateProcess(0, "hg > test.txt", ...) does not?
The code below creates a console-less process with stdout and stderr redirected to the specified file.
#include <windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(_T("out.log"),
FILE_APPEND_DATA,
FILE_SHARE_WRITE | FILE_SHARE_READ,
&sa,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL );
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL ret = FALSE;
DWORD flags = CREATE_NO_WINDOW;
ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );
ZeroMemory( &si, sizeof(STARTUPINFO) );
si.cb = sizeof(STARTUPINFO);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = NULL;
si.hStdError = h;
si.hStdOutput = h;
TCHAR cmd[]= TEXT("Test.exe 30");
ret = CreateProcess(NULL, cmd, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi);
if ( ret )
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
return -1;
}
You can't use stdout redirection in the command line passed to CreateProcess. To redirect stdout you need to specify a file handle for the output in the STARTUPINFO structure.
You are also making another, more subtle, mistake. The second parameter, lpCommandLine must point to writeable memory because CreateProcess overwrites the buffer. If you happen to be using the ANSI version of the function then you will get away with this, but not for the Unicode version.
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
Microsoft has an example how to redirect the standard output:
http://msdn.microsoft.com/en-us/library/ms682499(VS.85).aspx.
CreateProcess() launches processes, it is not a command line itnerpreter. It doesn't know what ">" is and won't do the stream redirection for you. You need to open the file test.txt yourself and pass the handle to it to CreateProcess inside the STARTUPINFO structure:
CreateProcess
STARTUPINFO
you should run process cmd.exe with params "/c command line".
This will redirect the output to a file or to organize a pipeline through CreateProcess.