Redirecting CreateProcess input stream to a file - c++

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

Related

printf printig inconsistent garbage when char array is returned to main [duplicate]

This question already has answers here:
Function returning array but main showing garbage [duplicate]
(3 answers)
Why is my function returning garbage when it should return a char?
(3 answers)
Closed 2 years ago.
I am capturing the output of a command run through CreateProcess after redirecting the output to a file and then reading the file contents. I store the string read from the file into a char array inside the function and print it. All good so far. Then I return the char array to the main function and attempt to print it from there. It seems to print inconsistent garbage. I am not sure why. Throwing the executable in a debugger, I see that the printf inside main is called with correct pointer address. I am at loss understanding why it behaves the way it does. Need some pointers. Here is my code:
#include <Windows.h>
#include <stdio.h>
char * run_cmd(char * cmd ) {
char output[2000];
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE hFile;
hFile = CreateFileA("out.log",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
&sa, // this seems important!
CREATE_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 = NULL;
si.hStdOutput = hFile;
ret = CreateProcessA(NULL, cmd, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi);
Sleep(2000);
CloseHandle(hFile);
DWORD lpNumberOfBytesRead; // return value
hFile = CreateFileA("out.log",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD dwBytesToRead = GetFileSize(hFile, NULL);
ReadFile(hFile, (void *)output, dwBytesToRead, &lpNumberOfBytesRead, NULL);
output[lpNumberOfBytesRead] = '\0';
CloseHandle(hFile);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
printf("%s\n", output);
printf("------------------------------------------------------------------------");
return output;
}
int main(void) {
printf("%s\n", run_cmd("ipconfig"));
}
``

How can I use LPSTARTUPINFO and LPPROCESSINFO properly in CreateProcessA?

I tried to use the tutorials around about using the CreateProcessA wherein it uses the STARTUPINFOA and LPPROCESS_INFORMATION.
However, when I tried to use the LPSTARTUPINFOA and LPPROCESS_INFORMATION and sadly it didn't work.
//Working
STARTUPINFOA startUpInfo = {0};
PROCESS_INFORMATION processInformation = {0};
startUpInfo.cb = sizeof(STARTUPINFOA);
CreateProcessA(NULL, "c:\\windows\\system32\\calc.exe", NULL, NULL, FALSE, 0, NULL, NULL, &startUpInfo, &processInformation);
//Fails
LPSTARTUPINFOA startUpInfo;
LPPROCESS_INFORMATION processInformation;
startUpInfo = (LPSTARTUPINFOA)malloc(sizeof(startUpInfo));
processInformation = (LPPROCESS_INFORMATION)malloc(sizeof(processInformation));
ZeroMemory(startUpInfo, sizeof(STARTUPINFOA));
ZeroMemory(processInformation, sizeof(PROCESS_INFORMATION));
startUpInfo->cb = sizeof(STARTUPINFOA);
CreateProcessA(NULL,"c:\\windows\\system32\\calc.exe", NULL, NULL, FALSE, 0, NULL, NULL, startUpInfo, processInformation);
I expect that both would work successfully, however, I wonder why LPSTARTUPINFOA and LPPROCESS_INFORMATION fails. Is there anyone who can enlighten me about my error in this program below.

How to execute commands from an attached console

I'm coding a WinAPI GUI program that needs calling ftp and possibly other console programs while getting their console output to act accordingly ie. waiting for ftp to complete execution before reading all its output wouldn't do.
My current approach is calling CreateProcess() to create a cmd.exe process potentially hiding the ugly console window, AttachConsole() to make it my own, GetStdHandle() to get input and output handles, SetConsoleCursorPosition() to the end of the console buffer, and WriteConsole() with commands such as ftp\n or dir\n. Yet this commands are written but not executed. However, I can manually use the same console ( using CreateProcess() with CREATE_NEW_CONSOLE flag ) to type ftp press enter and get it executed.
Previous approaches involved:
Calling ftp directly with CreateProcess() and redirected inputs/outputs.
Couldn't get ftp output until the CreateProcess() process had already ended.
Using system().
Was advised against its usage before getting any output.
My current stripped down code:
// Next two structures might be a bit misleading, they were used for the 1. previous
// approach
PROCESS_INFORMATION piProcInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION));
STARTUPINFO siStartInfo;
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;
SECURITY_ATTRIBUTES security;
security.nLength = sizeof(SECURITY_ATTRIBUTES);
security.lpSecurityDescriptor = NULL;
security.bInheritHandle = FALSE;
CreateProcess( NULL, "cmd", &security, &security, FALSE, NORMAL_PRIORITY_CLASS |
CREATE_NEW_CONSOLE, NULL, NULL, &siStartInfo, &piProcInfo);
uint32_t pidConsole = piProcInfo.dwProcessId;
while ( ! AttachConsole(pidConsole) ){};
HANDLE myConsoleIn, myConsoleOut;
myConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
myConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
Sleep(100);
CONSOLE_SCREEN_BUFFER_INFO myConsoleCursorInformation = {};
GetConsoleScreenBufferInfo(myConsoleOut,&myConsoleCursorInformation);
SetConsoleCursorPosition(myConsoleOut,myConsoleCursorInformation.dwSize);
CHAR myConsoleBuffer[200]="dir\n";
DWORD myConsoleProcessed;
WriteConsole( myConsoleOut, myConsoleBuffer, 4, &myConsoleProcessed, NULL);
How can I get a command written in the console to execute? Is there an alternative to my attempt of ending commands with a trailing \n ie. using WriteConsole() with a dir\n or ftp\n argument.
I thought about sending a keypress to the process in question after typing the desired command. Yet the created console needs not only to manually press the enter key but also having dir, ftp or whatever command to be manually typed.
Please feel free to point out any missing information !
How can I get a command written in the console to execute? Is there an
alternative to my attempt of ending commands with a trailing \n ie.
using WriteConsole() with a dir\n or ftp\n argument.
Try the following code to see if it works:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
const wchar_t *cmdPath = L"C:\\Windows\\System32\\cmd.exe";
wchar_t *cmdArgs = (wchar_t *)L"C:\\Windows\\System32\\cmd.exe /k dir";
BOOL result = CreateProcess(cmdPath, cmdArgs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
DWORD errCode = GetLastError();
if (!result)
{
std::cout << "Create Process failed: " << GetLastError() << std::endl;
}
/K Run Command and then return to the CMD prompt.
This is useful for testing, to examine variables
Use /C if you want "Run Command and then terminate".
Update: Complete code for communicating with a child process(cmd.exe) using pipes.
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
#define BUFSIZE 1024
void ErrorExit(LPCTSTR lpszFunction)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(1);
}
void ReadFromPipe(void)
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
for (;;)
{
DWORD bytesAvail = 0;
if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytesAvail, NULL)) {
std::cout << "Failed to call PeekNamedPipe" << std::endl;
}
if (bytesAvail) {
DWORD n;
BOOL success = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &n, NULL);
if (!success || n == 0) {
}
bSuccess = WriteFile(hParentStdOut, chBuf,n, &dwWritten, NULL);
}
else
{
break;
}
}
}
void WriteToPipe(void)
{
DWORD dwWritten;
BOOL bSuccess = FALSE;
CHAR buf[] = "dir\n";
bSuccess = WriteFile(g_hChildStd_IN_Wr, buf, sizeof(buf)-1, &dwWritten, NULL);
}
int main()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES saAttr;
printf("\n->Start of parent execution.\n");
// 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 STDOUT.
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
ErrorExit(TEXT("StdoutRd CreatePipe"));
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdout SetHandleInformation"));
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(TEXT("Stdin CreatePipe"));
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdin SetHandleInformation"));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(STARTUPINFO);
si.hStdError = g_hChildStd_OUT_Wr;
si.hStdOutput = g_hChildStd_OUT_Wr;
si.hStdInput = g_hChildStd_IN_Rd;
si.dwFlags |= STARTF_USESTDHANDLES;
TCHAR cmdPath[] = TEXT("C:\\Windows\\System32\\cmd.exe");
BOOL result = CreateProcess(cmdPath, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
DWORD errCode = GetLastError();
if (!result)
{
std::cout << "Create Process failed: " << GetLastError() << std::endl;
}
for (;;)
{
ReadFromPipe();
WriteToPipe();
}
}

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

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.

CreateProcess can not run a application

I just want to create a process to run a application on windows my code as below:
//init the structure
STARTUPINFOW StartupInfo;
ZeroMemory(&StartupInfo,sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow = true ;
PROCESS_INFORMATION ProcessInfo;
ZeroMemory(&ProcessInfo,sizeof(ProcessInfo));
DWORD dwExitCode = 0;
LPCWSTR cmdFormat = "xxxxxx"; // this is the applocation's path
LPWSTR cmd = new wchar_t[256*sizeof(wchar_t)];
wcscpy_s(cmd, wcslen(cmdFormat)+1,cmdFormat);
int ret = CreateProcessW(cmd,
NULL,
NULL,
NULL,
false,
NORMAL_PRIORITY_CLASS,
NULL,
NULL,
&StartupInfo,
&ProcessInfo);
if(ret)
{
CloseHandle(ProcessInfo.hThread);
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
CloseHandle(ProcessInfo.hProcess);
}
if(dwExitCode==0)
{
DWORD errorcode = GetLastError();
std::cout<<"ERROR: "<<errorcode<<std::endl;
}
I use this function that I can create new process to run notepad.exe and some other applications
Q1: but when I close the application the dwExitCode = 0 and the errorcode 1803
Q2: some application can not run just exit immediately
Following function always works for me:
static int createProcess(string cmdLine, bool isWait, LPDWORD pExitCode)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
::ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
::ZeroMemory(&pi, sizeof(pi));
// reset last error
::SetLastError(0);
// Start the child process.
BOOL bCreateProcess = ::CreateProcessA(NULL, // No module name (use command line)
(LPSTR) cmdLine.c_str(), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
CREATE_NO_WINDOW, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi); // Pointer to PROCESS_INFORMATION structure
if(!bCreateProcess)
{
// create process failed,
//Logger::trace(error, getClassName(), "createProcess", getFormattedStringA("create process failed with error:%d, Commad line:'%s',isWait:%d",GetLastError(), cmdLine.c_str(), isWait),"CreateProcess Failed");
return 0;
}
//Logger::trace(info, getClassName(), "createProcess", getFormattedStringA("created process,Commad line:'%s',isWait:%d,Result:%d", cmdLine.c_str(), isWait,bCreateProcess),"Launched Process");
// Wait until child process exits.
if(isWait)
{
::WaitForSingleObject(pi.hProcess, INFINITE);
if(pExitCode)
{
::GetExitCodeProcess(pi.hProcess, pExitCode);
}
}
::CloseHandle( pi.hProcess );
pi.hProcess = NULL;
::CloseHandle( pi.hThread );
pi.hThread = NULL;
return 1; // return non zero. function succeeded
}
Yes I find the root cause some application need some local resource ,so maybe need the parent's starting directory