I'm currently trying to use CreateProcess with the Path, Arguments and Environment Variables. My variables are stored in strings.
In the below example filePath and cmdArgs work fine, but I cannot get the envVars to work.
std::string filePath = "C:\\test\\DummyApp.exe";
std::string cmdArgs = "Arg1 Arg2 Arg3";
std::string envVars = "first=test\0second=jam\0"; // One
//LPTSTR testStr = "first=test\0second=jam\0"; // Two
CreateProcess(
LPTSTR(filePath.c_str()), //path and application name
LPTSTR(cmdArgs.c_str()), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
TRUE, // Set handle inheritance
0, // Creation flags
LPTSTR(envVars.c_str()), // environment block
//testStr //this line works
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) // Pointer to PROCESS_INFORMATION structure
)
When I run this code the error that comes back is "error 87: The parameter is incorrect".
What I don't understand is that if I comment out the line labeled "one" and replace it with the line labeled "two" (and make the matching swap in the function call) then it works correctly.
The constructor of std::string you used will copy "first=test\0second=jam\0" until first \0 (C-style string).
To pass all the string use another constructor:
std::string envVars("first=test\0second=jam\0", 22);
^^^^^^^^^^^^^^^^^^^^^^^^ ^
|
22 characters -------+
Related
I figured out how to open a process using CreateProcessAsUserA() from this:
example code: A service calls CreateProcessAsUser() I want the process to run in the user's session, not session 0
Now, I need to add process arguments to run the program correctly, I mean arguments like -steam.
I can't find any solution to do it on Google. Please help me.
The API is defined as so:
BOOL CreateProcessAsUserA(
HANDLE hToken,
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
You can just tack the command line on to the end of the target EXE.
So, using the example from above, it would look like this:
rc = CreateProcessAsUserA(hUserToken, // user token
0, // app name
(LPSTR)"c:\\foo.exe -steam", // command line
0, // process attributes
0, // thread attributes
FALSE, // don't inherit handles
DETACHED_PROCESS, // flags
0, // environment block
0, // current dir
&si, // startup info
&pi);
The "A" version of this method doesn't need to be non-const for lpCommandLine, the "W" version, on the other hand, does.
If the path to the executable has spaces in it, you will want to surround it in quotes:
(LPSTR)"\"c:\\my files\\foo.exe\" -steam"
ETA:
There was some confusion about how the commandline would be generated for the target program. To keep things C-style (argv[0] being the path to the target executable), you should not use the lpApplication parameter, and if you do, you would still want the lpCommandLine to look as it does above.
Info from the docs:
If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module to execute, and *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.
If you find it tricky to get the quotes right, you could create a function to do it.
C++17:
#include <initializer_list>
#include <string_view>
#include <utility>
// A function to prepare a commandline for execution by quoting
auto prep_cmd(std::initializer_list<std::string_view> args) {
if(args.size()==0) throw std::runtime_error("No command, no fun");
auto it = args.begin();
std::string AppName(*it); // AppName is returned unchanged
std::string CmdLine('"' + AppName + '"'); // but quoted when used in the commandline
for(++it; it != args.end(); ++it) {
CmdLine += ' ' + std::string(*it); // add argument unquoted
// CmdLine += " \"" + std::string(*it) + '"'; // or quote the argument too
}
return std::pair{AppName, CmdLine}; // return the result
}
Then call it:
auto[AppName, CmdLine] = prep_cmd({
R"aw(C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\csgo.exe)aw",
"-steam"
});
Use the result:
CreateProcessAsUserA(
hToken,
AppName.c_str(), // const char*
CmdLine.data(), // char*
...
);
I have tried to use this example to run an external program using CreateProcessW() in C++, however, when I use multiple arguments this code seems to not work.
In my case, I pass the following path:
std::string pathToExe = "C:\\Users\\Aitor - ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\xsim-runner.exe"
and the following arguments:
std::string arguments = "--model=facts_input.xml --output_xml=something.xml"
These parameters work from cmd, but they seem to not give any output (an xml should appear in the same folder) when I use them from C++.
Is there something I might be missing?
The following is an example for showing "How to run an exe with multiple arguments with CreateProcessW in C++". You can check if it helps.
The launcher application (a console app):
#include <iostream>
#include <windows.h>
int main()
{
STARTUPINFO si;
PROCESS_INFORMATION pi; // The function returns this
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CONST wchar_t* commandLine = TEXT("arg1 arg2 arg3");
// Start the child process.
if (!CreateProcessW(
L"D:\\Win32-Cases\\TestTargetApp\\Debug\\TestTargetApp.exe", // app path
(LPWSTR)commandLine, // 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());
throw std::exception("Could not create child process");
}
else
{
std::cout << "[ ] Successfully launched child process" << std::endl;
}
}
The target application (another console app) that will be launched:
#include <iostream>
#include <windows.h>
int main(int argc, char *argv[])
{
if (argc > 0)
{
for (int index = 0; index < argc; index++)
{
std::cout << argv[index] << std::endl;
}
}
return 1;
}
There's two potential problems I can infer from the code you're showing.
Space before the arguments
Depending on how you're concatenating the arguments string to the executable string, you may miss a space before the arguments. Without the code, it's impossible to tell, but try changing the arguments string like this :
std::string arguments = " --model=facts_input.xml --output_xml=something.xml;"
Current directory
CreateProcess spawns a child process that inherits the current directory from it's parent process. The XML files you specify on the arguments use relative paths.
Try specifying the full path of the XML files you're passing in the arguments, something like this :
std::string arguments = " --model=\"C:\\Users\\Aitor - ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\facts_input.xml\" --output_xml=\"C:\\Users\\Aitor - ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\something.xml\"";
You have to pass the complete command line in the arguments as under:
std::string arguments = "C:\\Users\\Aitor-ST\\Documents\\QtProjects\\ErgoEvalPlatform\\ErgonomicEvaluationPlatform\\FACTS\\xsim-runner.exe --model=facts_input.xml --output_xml=something.xml"
The second parameter of CreateProcessW requires complete command line and not just arguments. It passes this to the process and if the target process is a C program taking agrs, then as usual the first parameter will be module name and others that follow will be args.
Hope this helps
What I need to do is to run (for example) 6 copies (independent processes) of example.exe (what is also my program) with different command line arguments from my Main program. This copies should work at the same time. I'm using code like this:
const int NumberOfProcesses= 6;
STARTUPINFO si[NumberOfProcesses];
PROCESS_INFORMATION pi[NumberOfProcesses];
srand((unsigned)time(NULL));
for(int i=0;i<NumberOfProcesses;i++)
{
char fname[MAX_PATH];
strncpy(fname,"\"",1);
fname[1] = '\0';
strcat(fname,"d:\\test\\example.exe");
strcat(fname,"\"");
int id = i;
strcat(fname," ");
strcat(fname,(std::to_string(id)).c_str());
int count = (rand()%1000) + 1;
strcat(fname," ");
strcat(fname,(std::to_string(count)).c_str());
int lb = 13;
strcat(fname," ");
strcat(fname,(std::to_string(lb)).c_str());
int ub = 666;
strcat(fname," ");
strcat(fname,(std::to_string(ub)).c_str());
printf(fname);
cout<<"\n";
//Here in fname I have correct command, that runs properly
bool t = false;
t=CreateProcess( NULL, // No module name (use command line)
(LPSTR)fname, // Command line CharToLPWSTR(fname2)
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[i], // Pointer to STARTUPINFO structure
&pi[i] ); // Pointer to PROCESS_INFORMATION structure
}
So if NumberOfProcesses==0 it will run "d:\test\example.exe" 1 2 3 4 from fname. But if NumberOfProcesses==6 (or something else) zero iteration will complete properly, but others will return false. Sometimes 4th iteration runs properly.
I think this is because when zero iteration run "d:\test\example.exe" 1 2 3 4 the example.exe is busy and can not be run one more time. So I changed code to this:
bool t = false;
getchar();
for(int i=0;i<5;i++)
{
t=CreateProcess( NULL, // No module name (use command line)
(LPSTR)fname, // Command line CharToLPWSTR(fname2)
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[i], // Pointer to STARTUPINFO structure
&pi[i] ); // Pointer to PROCESS_INFORMATION structure
if(t) break;
}
So I got some delay between starting example.exe - and it helped. All 6 copies starts and finishes, but they don't run parallel (I have output from example.exe).
But this is not the way I want my program work.
How can I avoid this problem?
Thanks.
UPD.
According to Werner Henze's answer I've just add couple of lines (to initialize STURTUPINFO) into loop
const int NumberOfProcesses= 6;
STARTUPINFO si[NumberOfProcesses];
PROCESS_INFORMATION pi[NumberOfProcesses];
for(int i=0;i<NumberOfProcesses;i++)
{
///Next two lines is important
ZeroMemory( &si[i], sizeof(si[i]) );
si[i].cb = sizeof(si);
/*Some actions*/
t=CreateProcess( NULL, // No module name (use command line)
(LPSTR)fname, // Command line CharToLPWSTR(fname2)
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[i], // Pointer to STARTUPINFO structure
&pi[i] ); // Pointer to PROCESS_INFORMATION structure
}
And it is work fine now.
Thanks again.
In the cases where CreateProcess returns FALSE you should check GetLastError(). In the code that you posted you are not initializing the si array. STARUPINFO is an input paramter to CreateProcess, so you must initialize it before passing it to CreateProcess.
I guess example.exe is running so fast that they are terminated before you come to look at them. If you printf a timing value like GetTickCount() after each CreateProcess, you will see that all CreateProcess calls occur very fast.
I'm somewhat confused about the proper way of calling CreateProcessAsUser with command line parameters. So without going into details of filling out the rest of its parameters, can someone confirm that this is how it should be done? (In other words, should I put the exe file path as the first command line parameter, or specifying it as lpApplicationName is enough?)
LPCTSTR pExePath = L"c:\\program files\\sub dir\\program.exe";
LPCTSTR pCmdLine = L"v=\"one two\"";
TCHAR buff[MAX_PATH];
StringCchCopy(buff, MAX_PATH, _T("\""));
StringCbCat(buff, MAX_PATH, pExePath);
StringCbCat(buff, MAX_PATH, _T("\" "));
StringCbCat(buff, MAX_PATH, pCmdLine);
CreateProcessAsUser(hToken, pExePath, buff, NULL, NULL, FALSE, dwFlags, NULL, NULL, &si, &pi);
If 2nd param to CreateProcessAsUser is NULL, then the module name must be the first white space–delimited token in the 3rd param.
If 2nd param to CreateProcessAsUser is not NULL, then it will be taken as the executable to execute. In this case, the 3rd param can either be
a) "EXENAME p1 p2"
or it can be
b) "p1 p2"
If you chose a), then the child process will have the following
argv[0] --> EXENAME
argv[1] --> p1
argv[2] --> p2
If you chose b), then the child process will have
argv[0] --> p1
argv[1] --> p2
Either way, the process to be executed would be EXENAME (the 2nd param to CreateProcessAsUser). The called process however should be aware of the way command line arguments are going to be coming in.
If you use b), you also have the option of passing 2nd param to CreateProcessAsUser as NULL.
I'm trying to write a few simple lines of code that will get the 'Program Files' dir path on both XP and Vista/7 (on vista/7 I need the path to the x86 folder), add some extra path to an application and execute it.
This is what I have so far, but it's not executing the external program, not giving an error as well..
wchar_t localAppData[MAX_PATH];
STARTUPINFO sInfo;
PROCESS_INFORMATION pInfo;
SHGetFolderPath(NULL, CSIDL_PROGRAM_FILESX86, 0, NULL, localAppData);
std::wstringstream ss;
ss << localAppData << L"/MyApp/MyExe.exe";
LPCWSTR str = ss.str().c_str();
CreateProcess(str, NULL,NULL, NULL,FALSE,NULL,NULL,NULL,&sInfo,&pInfo);
return str;
I've updated my code to this according to hmjd's suggestion:
wchar_t localAppData[MAX_PATH];
STARTUPINFO sInfo = { sizeof(STARTUPINFO), NULL, L"winsta0\\default" };
PROCESS_INFORMATION pInfo;
SHGetFolderPath(NULL, CSIDL_PROGRAM_FILESX86, 0, NULL, localAppData);
std::wstringstream ss;
ss << localAppData << L"/PacificPoker/bin/888poker.exe";
std::wstring ss_str = ss.str();
wchar_t* path = new wchar_t[ss_str.length() + 1]();
std::copy(ss_str.begin(), ss_str.end(), path);
CreateProcess(path, NULL,NULL, NULL,FALSE,NULL,NULL,NULL,&sInfo,&pInfo);
delete[] path;
return GetLastError();
Still getting '3' for GetLastError, but I can confirm that C:\Program Files\MyApp\MyExe.exe exists..
A few problems with the code:
sInfo is not initialised, you at least need to set the cb member
STARTUPINFO sInfo = { sizeof(STARTUPINFO), // 'cb'
NULL, // 'lpReserved'
L"winsta0\\default" };// 'lpDesktop'
See STARTUPINFO for more details.
the first argument to CreateProcess() should be non-const, but is being passed c_str() which would be a const if it was not a dangling pointer. The ss.str() method returns a std::string, and the c_str() is returning a pointer into that std::string but it is a temporary object and is destroyed at the end of the expression (the ;), making str a dangling pointer. Change to:
std::wstring ss_str = ss.str();
wchar_t* path = new wchar_t[ss_str.length() + 1]();
std::copy(ss_str.begin(), ss_str.end(), path);
CreateProcess(path, ...);
delete[] path;
Check return values of all your functions and query GetLastError() to determine failure reason.
You've probably tried this, but does localAppData get a valid string if you set the CSIDL to CSIDL_PROGRAM_FILES instead of CSIDL_PROGRAM_FILESX86?
If you're testing under XP, I don't know what CSIDL_PROGRAM_FILESX86 will return.