createprocess doesn't inherit parent env - c++

I have written the following code to create a process on cygwin that run a script
DWORD RunSilent(char* strFunct, char* strstrParams)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
char Args[4096];
char *pEnvCMD = NULL;
char *pDefaultCMD = "CMD.EXE";
ULONG rc;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = sizeof(STARTUPINFO);
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow = SW_HIDE;
Args[0] = 0;
strcat(Args, strFunct);
strcat(Args, " ");
strcat(Args, strstrParams);
if (!CreateProcess( NULL, Args, NULL, NULL, TRUE,
/*CREATE_NEW_CONSOLE, */
0,
NULL,
NULL,
&StartupInfo,
&ProcessInfo))
{
return GetLastError();
}
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
if(!GetExitCodeProcess(ProcessInfo.hProcess, &rc))
rc = 0;
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
return rc;
}
int main(){
RunSilent("c:/cygwin64/bin/bash.exe", " --login -i -c \"/cygdrive/c/FP/V7/myscript \"");
return 0;
}
the content of myscript is :
if test $MY_HOME
then
FORMALHOME_HOME=$MY_HOME
export FORMALHOME_HOME
else
echo "A Home variable is required: missing \$MY_HOME"
exit -1
fi
by running my program it always returns the message "A Home variable is required: missing $MY_HOME"
Although the variable $MY_HOME already set
seems that the created process doesn't inherit the parent environment
Any idea to solve this?

Not sure if this solves all of your problem, but the --login-option of bash clears all environment variables and provides a clean shell. Have you tried leaving out this option?

Related

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();
}
}

C++ CreateProcess 'telnet' is not recognized

When I pass the ipconfig command to the process, it stores the correct results in files.
char cmd[] = "C:\\windows\\system32\\cmd.exe /c ipconfig";
SaveResult("ipconfig1.txt", NULL, cmd);
char appName[] = "C:\\windows\\system32\\cmd.exe";
char cmd2[] = "/c ipconfig";
SaveResult("ipconfig2.txt", appName, cmd2);
But when i pass wuauclt or telnet
char cmd1[] = "C:\\windows\\system32\\cmd.exe /c telnet";
SaveResult("telnet1.txt", NULL, cmd1);
char appName3[] = "C:\\windows\\system32\\cmd.exe";
char cmd3[] = "/c telnet";
SaveResult("telnet2.txt", appName3, cmd3);
char cmd4[] = "C:\\windows\\system32\\cmd.exe /c wuauclt";
SaveResult("wuauclt1.txt", NULL, cmd4);
char appName5[] = "C:\\windows\\system32\\cmd.exe";
char cmd5[] = "/c wuauclt";
SaveResult("wuauclt2.txt", appName5, cmd5);
I get
'wuauclt' is not recognized as an internal or external command,
operable program or batch file.
'telnet' is not recognized as an internal or external command,
operable program or batch file.
How to fix this problem and why it happens? Do it possible to launch through cmd.exe telnet or wuauclt?
Also on this PC wuauclt and telnet in common console opened from start working like expected.
#include "stdafx.h"
#include "windows.h"
wchar_t *convertCharArrayToLPCWSTR(const char* charArray)
{
wchar_t* wString = new wchar_t[4096];
MultiByteToWideChar(CP_ACP, 0, charArray, -1, wString, 4096);
return wString;
}
void SaveResult(const char *fileName, const char *appName, const char *commandLine)
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(convertCharArrayToLPCWSTR(fileName),
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;
ret = CreateProcess(appName==NULL ? NULL : convertCharArrayToLPCWSTR(appName), commandLine == NULL ? NULL : convertCharArrayToLPCWSTR(commandLine), NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi);
if (ret)
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(h);
}
}
int main()
{
char cmd[] = "C:\\windows\\system32\\cmd.exe /c ipconfig";
SaveResult("ipconfig1.txt", NULL, cmd);
char appName[] = "C:\\windows\\system32\\cmd.exe";
char cmd2[] = "/c ipconfig";
SaveResult("ipconfig2.txt", appName, cmd2);
char cmd1[] = "C:\\windows\\system32\\cmd.exe /c telnet";
SaveResult("telnet1.txt", NULL, cmd1);
char appName3[] = "C:\\windows\\system32\\cmd.exe";
char cmd3[] = "/c telnet";
SaveResult("telnet2.txt", appName3, cmd3);
char cmd4[] = "C:\\windows\\system32\\cmd.exe /c wuauclt";
SaveResult("wuauclt1.txt", NULL, cmd4);
char appName5[] = "C:\\windows\\system32\\cmd.exe";
char cmd5[] = "/c wuauclt";
SaveResult("wuauclt2.txt", appName5, cmd5);
return -1;
}
If you type in ipconfig in console window, the process will show IP information and exit.
On the other hand, if you type in telnet in console window, the process will show a prompt and waits for a response. The process does not finished automatically.
When you run this command with CreateProcess, CreateProcess will return immediately, but the process is not finished. Then you try to close the file handle which is still being used by telnet.
You can use WaitForSingleObject to wait until the process is complete. In the case of telnet the process doesn't complete. The example below demonstrates this problem.
For CreateProcess, supply the whole command line as the second parameter. Make sure the character buffer is writable, and freed at the end.
Side note, it is recommended to use wide character string for a Unicode program. It's fine to promote ANSI to UTF16, but not much is gained in this case. You can also use CreateProcessA along with STARTUPINFOA si = { sizeof(si) }; which accept ANSI character.
void SaveResult(const wchar_t *fileName, const wchar_t *commandLine)
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(fileName, FILE_WRITE_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ,
&sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(h == INVALID_HANDLE_VALUE)
return;
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { sizeof(si) };
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = NULL;
si.hStdError = h;
si.hStdOutput = h;
wchar_t *writable_cmdline = _wcsdup(commandLine);
BOOL success = CreateProcess(NULL, writable_cmdline,
NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
bool finished = false;
//wait for 1 second
for(int i = 0; i < 10; i++)
{
if(WaitForSingleObject(pi.hProcess, 100) <= 0)
{
finished = true;
break;
}
}
if(success)
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
CloseHandle(h);
free(writable_cmdline);
if(!finished)
printf("Process didn't finish\n");
}
int main()
{
SaveResult(L"telnet.txt", L"C:\\windows\\system32\\cmd.exe /c telnet");
SaveResult(L"ipconfig.txt", L"C:\\windows\\system32\\cmd.exe /c ipconfig");
return 0;
}

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

shellexecute gives correct result if i entered bad dos command

I am executing dos command and gives me expected result.
//executing dos command
hInst = ShellExecute(0, "open", "cmd.exe", "/C dir > out.txt", 0, SW_HIDE);
if(int(hInst)>32)
{
cout<<"\n Command executed.";
}
else
{
cout<<"\n Command not executed.";
}
if i executed same code for a bad command..
hInst = ShellExecute(0, "open", "cmd.exe", "/C abc > out.txt", 0, SW_HIDE);
if(int(hInst)>32)
{
cout<<"\n Command executed.";
}
else
{
cout<<"\n Command not executed.";
}
still it shows the command executed which is not expected.
What can i do to check whether command(abc) is valid and executed successfully
Probably the simplest thing you can do is:
#include <cstdlib>
// ...
int ret1 = std::system("dir > out.txt"); // ret1 == 0
int ret2 = std::system("abc > out.txt"); // ret2 != 0
but it'll show the console black window.
Using ShellExecuteEx:
SHELLEXECUTEINFO ei = {0};
ei.cbSize = sizeof(SHELLEXECUTEINFO);
ei.fMask = SEE_MASK_NOCLOSEPROCESS;
ei.hwnd = NULL;
ei.lpVerb = NULL;
ei.lpFile = "cmd";
ei.lpParameters = "/c dir > out.txt";
ei.lpDirectory = NULL;
ei.nShow = SW_HIDE;
ei.hInstApp = NULL;
ShellExecuteEx(&ei);
WaitForSingleObject(ei.hProcess, INFINITE);
unsigned long ret;
GetExitCodeProcess(ei.hProcess, &ret);
// ret==0 ==> success ret!=0 ==> failure
Using CreateProcess:
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
unsigned long ret;
char cmd[255] = "cmd /c dir > out.txt";
if (CreateProcess(0,
cmd, // this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string)
0,
0, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
{
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &ret);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
// ret==0 ==> success ret!=0 ==> failure
Please consider that these are just examples to give an idea of what you could do. Real code will be similar in spirit but slightly more complex.

Get pid of started process c++

I try to get PID of process which i started by my app.
DWORD dwPid = GetProcessId(pi.hProcess);
Somewhere on this forum is this solution but i dont have func "GetProcessId"
To start process i'm using:
BOOL bSuccess = FALSE;
LPTSTR pszCmd = NULL;
PROCESS_INFORMATION pi;// = {0};
STARTUPINFO si = {0};
si.cb = sizeof(si);
pszCmd = ""; /* assign something useful */
bSuccess = CreateProcess("D:\\program\\program.exe",NULL, NULL, NULL, TRUE, 0, NULL, "D:\\program", &si, &pi);
if (bSuccess)
{
}
It is possible to run my code in this started program without dll ?
According to the documentation on PROCESS_INFORMATION, you can access the process id directly from the PROCESS_INFORMATION struct by accessing the dwProcessId member:
DWORD dwPid = pi.dwProcessId;