C Multiple processes: Program crashes while creating child process - windows

I am trying to create a child process with the following command:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
CreateProcess( NULL, // No module name (use command line)
NULL, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi );
It crashes over here and I am not sure why.
Now my original process takes command line parameters, so do I have to pass them here also? If so then since I am not creating child process from int main() so can I do the following:
LPTSTR szCmdline = TEXT("nmctest -s TS -r DMR -tlLDMR");
Then pass szCmdline inside CreateProcess()?
Can someone please help me why this is crashing?

Your code is failing because you are passing NULL for both lpApplicationName and lpCommandLine. You must pass a value for at least one of them. The documentation makes that clear.
It looks like you have also attempted to pass a value to lpCommandLine. But you have passed a non-modifiable string literal. Again the documentation makes it clear that is not allowed. Pass a pointer to memory that can be modified.
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.
You can meet that requirement like this:
TCHAR szCmdline[] = _T("nmctest -s TS -r DMR -tlLDMR");
Personally, I see no need for TCHAR in this day and age. Surely you aren't still writing programs for Windows 98? I would do it like so:
wchar_t szCmdline[] = L"nmctest -s TS -r DMR -tlLDMR";
The other possible failure vector in your code is the STARTUPINFO parameter. Make sure that you have initialised that correctly. The most simply way to do that is like so:
STARTUPINFO si = {0};
si.cb = sizeof(si);
But you might like to add a call to GetStartupInfo.
STARTUPINFO si = {0};
si.cb = sizeof(si);
GetStartupInfo(&si);

Related

Launch an .exe file from Win32

I have been trying to start an exe file from a Win32 application, however I have been unable to get it to work. I want to pass an argument to it as well, but I don't think I am doing it correctly. A similar question has been asked here before, but it seems like they wanted to run a command (cmd.exe), not start another exe file. Specifically, I want to launch the Java appletviewer.
My current code is this:
LPCWSTR pszViewerPath = L"C:\\Path\\to\\appletviewer.exe"; // I know that this path is correct
PWSTR pszFilePath;
// get the path to the HTML file to pass to appletviewer.exe, store it in pszFilePath...
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(pszViewerPath,
pszFilePath,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
The problem I am having is that a command prompt window briefly appears before disappearing without a trace.
What am I doing wrong? I was originally going to use ShellExcecute but read that that was inefficient.
How do I fix this? Thank you for your help.
When using both the lpApplicationName and lpCommandLine parameters of CreateProcess(), it is customary to repeat the application file path as the 1st command-line parameter. This is even stated in the CreateProcess() documentation:
If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.
Try something more like this:
LPCWSTR pszViewerPath = L"C:\\Path\\to\\appletviewer.exe"; // I know that this path is correct
PWSTR pszFilePath;
// get the path to the HTML file to pass to appletviewer.exe, store it in pszFilePath...
PWSTR pszCmdLine = (PWSTR) malloc((lstrlen(pszViewerPath) + lstrlen(pszFilePath) + 6) * sizeof(WCHAR));
if (!pszCmdLine)
{
// error handling...
}
else
{
wsprintf(pszCmdLine, L"\"%s\" \"%s\"", pszViewerPath, pszFilePath);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(
pszViewerPath,
pszCmdLine,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi))
{
// error handling...
}
else
{
// optional: wait for the process to terminate...
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
free(pszCmdLine);
}
In which case, there is no point in using the lpApplicationName parameter at all:
If lpApplicationName is NULL, the first white space–delimited token of the command line specifies the module name. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin
CreateProcess(NULL, pszCmdLine, ...)

ShellExecute bat file elevated (FMX, Win32)

I want to spawn a batch file from my FMX app (on Win32) with elevated privileges. From Remy's answer at the bottom of this thread on ShellExecute I found how to launch the batch file. Now, i can't figure out how to launch it with elevated privilege. Below is my code:
String Prog = "c:\\Users\\rwp\\Desktop\\test.bat";
int nErrorCode = (int) ShellExecute(NULL, L"runas", Prog.c_str(), NULL, NULL, SW_SHOWNORMAL);
if (nErrorCode <= 32) {
ShowMessage("an error occured");
}
I added "runas" for the second argument after reading this to no avail. Running the batch file manually (right-click and run as admin) works. Here is content of the batch file fyi (just kicks of a system imaging):
c:\Windows\system32\wbAdmin.exe start backup -backupTarget:D: -include:C: -allCritical -quiet
How can i ShellExecute this batch file as admin?
UPDATE 1: I'm attempting to use CreateProcess per Remy suggestion. Here is my code (based on this example):
//Code is inside a __fastcall button click
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.lpReserved = NULL;
siStartInfo.lpReserved2 = NULL;
siStartInfo.cbReserved2 = 0;
siStartInfo.lpDesktop = NULL;
siStartInfo.dwFlags = 0;
// String strCmdLine = "C:\\Users\\rwpatter\\Desktop\\test.bat";
String strCmdLine = "C:\\Windows\\System32\\wbAdmin.exe start backup -backupTarget:T: -include:C: -allCritical -quiet";
// Create the child process.
int rtrn = CreateProcess(
NULL,
strCmdLine.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
0, // handles are inherited
0, // creation flags
0, // use parent's environment
0, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// Wait for the processs to finish
DWORD rc = WaitForSingleObject(
piProcInfo.hProcess, // process handle
INFINITE);
ShowMessage(IntToStr(rtrn));
If I run it as shown (right-click on exe and run as admin) it returns 0 which means it failed. If I run it by putting the wbAdmin command line in the test.bat file (see commented line right above String strCmdLine in the code) then CreateProcess returns a 1 (success) but wbAdmin is still not running. It flashed a DOS window and i captured it as shown in the picture below. It shows oriental characters in the title bar and says not recognized as internal or external command. But, if i run that test.bat directly (elevated) it runs wbAdmin no problem.
Any ideas on what is wrong? Besides me obviously being ignorant. (p.s. i'll get to testing Golvind's answer on the ShellExecute after this...)
Running the batch file manually (right-click and run as admin) works.
Because you are running the 64-bit version of cmd when you start it manually.
It shows oriental characters in the title bar and says not recognized
as internal or external command.
Because your application is 32-bit. A 32-bit application does not see the same System32 folder as 64-bit applications. You can access the 64-bit System32 folder in 32-bit applications with the virtual sysnative folder.
#include <shellapi.h>
...
String strCmdLine = "wbAdmin.exe start backup -backupTarget:T: -include:C: -allCritical -quiet";
int rtrn = CreateProcess(
NULL,
strCmdLine.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
0, // handles are inherited
0, // creation flags
0, // use parent's environment
0, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
if (!rtrn)
{
String newCmdLine = "c:\\windows\\sysnative\\wbAdmin.exe start backup -backupTarget:T: -include:C: -allCritical -quiet";
rtrn = CreateProcess(
NULL,
newCmdLine.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
0, // handles are inherited
0, // creation flags
0, // use parent's environment
0, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
}
Or compile your application to 64-bit.
You need to launch CMD.exe as Administrator with "runas", and specify the batch file as a "run-me-then-exit" (i.e. /c) argument to command prompt, as so:
WCHAR wszCmdPath[MAX_PATH];
GetEnvironmentVariableW(L"ComSpec", wszCmdPath, MAX_PATH);
ShellExecuteW(NULL, L"runas", wszCmdPath, L"/c \"C:\\Path\\BatchFile.bat\"", L"", SW_SHOW);
Both functions called here can fail, and robust code would test for success before proceeding.

NtCreateProcess(Ex) - Can I have a child process inherit the parents address space while running under a different process name?

I am calling NtCreateProcessEx with the section handle argument set to NULL in order to create a user mode process that is initialized with a copy of the parents address space.
I want the child process to run under a different image name other than the one of the parent process.
Is this even possible?
Here's my call to NtCreateProcessEx:
HANDLE fileHandle;
OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
UNICODE_STRING InputString;
RtlInitUnicodeString( &InputString, L"C:\\Users\\user\\Documents\\codeblocks_projects\\test\\bin\\Release\\test.exe" );
ObjectAttributes.Length = sizeof( OBJECT_ATTRIBUTES );
ObjectAttributes.ObjectName = &InputString;
NTSTATUS status = NtCreateProcessEx( &fileHandle, PROCESS_QUERY_INFORMATION, &ObjectAttributes, GetCurrentProcess(), PS_INHERIT_HANDLES, NULL, NULL, NULL, FALSE );
printf_s( "%x\n", status );
Status is 0xC0000033 - STATUS_OBJECT_NAME_INVALID, if I don't pass any object attributes, the call works fine.
What am I missing here?
My guess is that this not only poorly documented; it is also impossible. At least, it seems effectively impossible nowadays, perhaps due to security concerns.
The documentation for NtOpenProcess indicates that even identifying a process by name hasn't been possible since Vista:
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-ntopenprocess
I just tried an alternative approach using NtSetInformationProcess:
#define PHNT_NO_INLINE_INIT_STRING
#include <phnt_windows.h>
#include <phnt.h>
#include <stdio.h>
int main() {
NTSTATUS status;
HANDLE handle;
status
= NtCreateProcess(&handle,
PROCESS_ALL_ACCESS,
NULL,
NtCurrentProcess(),
/*InheritObjectTable=*/TRUE,
NULL,
NULL,
NULL
);
UNICODE_STRING newName;
RtlInitUnicodeString(&newName, L"dummy.exe");
status
= NtSetInformationProcess(
handle,
ProcessImageFileName,
&newName,
sizeof newName
);
// fails with 0xC0000003, STATUS_INVALID_INFO_CLASS
printf("status: %x\n", status);
// pause to observe the new "zombie child" in Process Explorer
printf("sleeping...\n");
Sleep(5000);
return 0;
}
As noted in the code, NtSetInformationProcess fails with STATUS_INVALID_INFO_CLASS, even though this is allowed by the corresponding NtQueryInformationProcess:
https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
Looking at the ReactOS source, this seems to be a deliberate omission. I also found this:
https://reactos.org/pipermail/ros-diffs/2004-November/002066.html
As of this writing, the above reads "don't allow the ProcessImageFileName information class for NtSetInformationProcess() anymore".
All this suggests it may have been possible in the past. After all, how was exec() in the old POSIX subsystem implemented? After fork() created an initial thread in the new process and set the thread's context to be a copy of the parent's, the child might then call exec(), whereupon the POSIX implementation probably destroyed and re-created the child process from within. At some point, the subsystem would have to set the ProcessImageFileName somehow.

Windows API - CreateProcess() path with space

How do I pass path with space to the CreateProcess() function?
The following works
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
if( !CreateProcess(_T("c:\\installer\\ew3d.exe"), // No module name (use command line)
_T("c:\\installer\\ew3d.exe /qr"),//argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // 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
)
{
printf( "CreateProcess failed (%d).\n", GetLastError() );
return false;
}
//Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
But if I use a path with space as as the code below, it didn't work.
CreateProcess(_T("c:\\master installer\\ew3d.exe"), // No module name (use command line)
_T("c:\\master installer\\ew3d.exe /qr"),//argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // 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
)
And quoting the command such as below didn't help too
CreateProcess(_T("\"c:\\master installer\\ew3d.exe\""), // No module name (use command line)
_T("\"c:\\master installer\\ew3d.exe\" /qr"),//argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // 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
)
What is the right way to pass in the path with space?
In response to another answer, example #3 is NOT the correct one.
The issue is that the quotes should NOT encapsulate the module pathname passed as the first parameter of CreateProcess. However, quotes SHOULD encapsulate arg0 (again module path) as passed for the command line (second parameter of CreateProcess).
So, the correct rendition would be:
CreateProcess(_T("c:\\master installer\\ew3d.exe"),
_T("\"c:\\master installer\\ew3d.exe\" /qr"),
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // 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
)
Your 3rd snippet is the correct one, not sure why you have trouble. Having the GetLastError() return value would be valuable here. Do note however that the 2nd argument of CreateProcess is an LPTSTR, not an LPCTSTR. In other words, Windows can write back to the string. Pretty creepy, isn't it? Enough reason perhaps to use ShellExecuteEx() instead.
You don't need to specify the application path in both the first and second arguments. According to the MSDN documentation the second argument should be only command line arguments if you list the application name in the first argument. Otherwise, set the first argument to NULL and then in the second argument enclose the application name in quotes if it contains a space. Not sure why your last listing doesn't work.
Docs are unclear, but it seems possible that if you include a space you must allow param 2 to define the full path.
The lpApplicationName parameter can be
NULL. In that case, the module name
must be the first white
space–delimited token in the
lpCommandLine string. If you are using
a long file name that contains a
space, use quoted strings to indicate
where the file name ends and the
arguments begin; otherwise, the file
name is ambiguous.
Have you tried this variation?
CreateProcess(NULL, // No module name (use command line)
_T("\"c:\\master installer\\ew3d.exe\" /qr"),//argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // 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
)
EDIT: The following worked for me (dwError is 0). My project is built with multibyte charset.
LPTSTR szCmdLine = _tcsdup(TEXT(
"\"C:\\Program Files\\adobe\\Reader 8.0\\reader\\acrord32.exe\" /qr"));
CreateProcess(NULL,
szCmdLine,
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // 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
); // This works. Downcasting of pointer to members in general is fine.
DWORD error = GetLastError();
A bit late to the party. For some reason, I cannot up-vote Praetorian, but he's right. I was suffering from the same problem, and NULLing the Application Name did the trick. I also tried path in App Name & just the command line params in the second argument, to no avail.
I am on Win7 x64.
CreateProcess (NULL, "\"Path to exe\" -x -y -z", ...);
works for me.

Making CreateProcess inherit the console of the calling process

When I call CreateProcess in Windows, the new process doesn't seem to inherit the console of the calling process. I made a test program that runs "ruby xtest", xtest being a script that writes "hello" to standard output. I ran this test program from Emacs, and get no output. I also tried the following code calling GetStdHandle, but again, no output. Then I tried passing CREATE_NEW_CONSOLE in dwCreationFlags to CreateProcess, which made a whole new window with the Ruby output. Finally, I made a simple fork/exec
test program and compiled it using Cygwin's GCC. This program worked: the Ruby output showed up in Emacs as expected. I tried to decipher the Cygwin source code in http://cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/cygwin/spawn.cc?rev=1.268&content-type=text/x-cvsweb-markup&cvsroot=src but failed. So, how do you make the new process inherit the console of the parent process such that the output from the child shows up as expected?
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if(!CreateProcess(0, "ruby xtest", 0, 0, 1, 0, 0, 0, &si, &pi)) die("CreateProcess");
I know, this thread is rather old, however, I just ran into the same problem.
Just as for the TS, the console handle was inherited and working fine under Cygwin, but not on a Windows console. Instead, the output on stdout was neither shown, nor any error was reported. Inherited Pipe handles worked still fine.
I took me some time to identify the (now obvious) problem: CreateProcess() was called with CREATE_NO_WINDOW. Dropping this flag, console output is fine. (Though, according to the code of the TS, they never set this flag in the first place.)
Hope this might be helpful for people who also stumble across this thread, like myself.
According to Microsoft documentation, lpCommandLine (2. parameter):
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.
When I stopped using a constant here it worked for me. I didn't need the STARTF_USESTDHANDLES and GetStdHandle thing.
This code from a console prg runs and outputs another console exe in the same console:
FillChar(SI, SizeOf(SI), 0);
SI.cb:=SizeOf(SI);
FillChar(PI, SizeOf(PI), 0);
if CreateProcess(nil, CmdLineVar, nil, nil, False, 0, nil, nil, SI, PI) then ...
I've done this by passing in pipes for hStdInput, hStdOutput, and hStdError and manually routing data from the hStdOutput and hStdError pipes to the console.
Not sure if debeige ever solved this, but I needed the same thing, but starting up another thread to listen to stdout output, just to put it on stdout seemed nuts to me.
The following works for me, and is slightly different than what he originally posted. I thought at first it wouldn't work if you don't set si.cb, but when I commented that in mine, it still worked, so... YMMV.
STARTUPINFO siStartInfo;
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdInput = g_hChildStd_IN_Rd; // my outgoing pipe
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(
NULL,
szCmdline,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&siStartInfo,
&piProcInfo);

Resources