Windows API - CreateProcess() path with space - winapi

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.

Related

Do I have to specify handles to be inherited with PROC_THREAD_ATTRIBUTE_LIST when using CreateProcess in threads?

I'm writing a program that spawns multiple child processes simultaneously and communicates to each of them by redirecting their stdin/stdout to anonymous pipes (as described here).
This is my actual code adapted from above example:
HANDLE h_child_stdout_r = NULL;
HANDLE h_child_stdout_w = NULL;
HANDLE h_child_stdin_r = NULL;
HANDLE h_child_stdin_w = NULL;
SECURITY_ATTRIBUTES saAttr;
BOOL bSuccess = TRUE;
// 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.
bSuccess &= CreatePipe(&h_child_stdout_r, &h_child_stdout_w, &saAttr, 0);
// Ensure the read handle to the pipe for STDOUT is not inherited.
bSuccess &= SetHandleInformation(h_child_stdout_r, HANDLE_FLAG_INHERIT, 0);
// Create a pipe for the child process's STDIN.
bSuccess &= CreatePipe(&h_child_stdin_r, &h_child_stdin_w, &saAttr, 0);
// Ensure the write handle to the pipe for STDIN is not inherited.
bSuccess &= SetHandleInformation(h_child_stdin_w, HANDLE_FLAG_INHERIT, 0);
if(!bSuccess)
{
throw dlg_exception("Failed to create child process handles");
}
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
siStartInfo.hStdError = h_child_stdout_w;
siStartInfo.hStdOutput = h_child_stdout_w;
siStartInfo.hStdInput = h_child_stdin_r;
DWORD processFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
// Create the child process.
bSuccess = CreateProcess(
const_cast<wchar_t *>(utf8::widen(exeName).c_str()), // application name
const_cast<wchar_t *>(utf8::widen(cmd).c_str()), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
processFlags, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
{
string msg = "Create process failed with error code ";
msg.append( std::to_string(GetLastError()) );
throw dlg_exception(msg.c_str());
}
// 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);
// Close handles to the stdin and stdout pipes no longer needed by the child process.
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
CloseHandle(h_child_stdout_w);
CloseHandle(h_child_stdin_r);
The above function will be called from multiple threads that are responsible for creating child processes and reading their output.
Everything works as expected but just to make sure I started searching about thread-safety of CreateProcess and I came across this article which in short says there will be an issue if multiple calls to CreateProcess are made from different threads because then each child process will inherit ALL handles from the parent process.
Now I have two questions:
1- Will this really be an issue for me? Since I am only redirecting the child process's stdin and stdout to the pipe handles, so I'm not sure if there will be a problem even if all the handles are inherited by the child. The child is merely writing to its own stdout and doesn't care about what handles it has inherited right?
2- I tried inspecting the child processes with Sysinternal's Process Explorer but as far as I could tell only two handles were inherited by each child not all of them.
Here's a screenshot of Process Explorer. As you can see the parent process has created 4 child processes. For each process I created, a new pair of handles would appear in parent process's list of handles (I have highlighted the first two):
As you can see there is four pairs of these, one for each child process.
And for each child process the handle list looks like this:
After reading the article what I was expecting was that the fourth child process would have all the handles listed because it's supposed to inherit all inheritable handles. But it only has two handles like all other childs.
So I'm wondering if I can get away with my current code without doing the PROC_THREAD_ATTRIBUTE_LIST which looks complicated and unfun.

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.

C Multiple processes: Program crashes while creating child process

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

Resources