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.
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
My code should take arguments, put "+" inbetween them, and search google chrome with this, however I get the error (Command line Argument= "Stack Overflow Site"):
http://www.google.com/search?q=Stack+Overflow+Site'C:\Program' is not
recognized as an internal or external command, operable program or
batch file.
Also in my program I get this error:
error C4996: 'strcpy': This function or variable may be unsafe.
Consider using strcpy_s instead. To disable deprecation, use
_CRT_SECURE_NO_WARNINGS. See online help for details. c:\users\user\documents\visual studio
2013\projects\project1\project1\main.cpp
I have been ignoring this, because I thought it was just a warning, but I'm not sure if it is relevant.
My code:
#include <Windows.h>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
//Loop through arguments and put a "+" between them.
string out = "";
for (int i = 1; i < argc; ++i) {
if (i != 1){
out += "+";
}
out += argv[i];
}
string newout = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \"http://www.google.com/search?q=" + out + "\"";
// set the size of the structures
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//set y to newout and convert
char *y = new char[newout.length() + 1];
strcpy(y, newout.c_str());
//Run Google Chrome with argument
CreateProcessA("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", // the path
y, // 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
);
delete[] y;
cin.ignore();
}
I suspect you are not getting exactly the same error since you
stopped deleting the commandline buffer before using it, but
some different, equally unexpected result.
If you will read the documentation of CreateProcess
you will learn that the parameters:
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
can be supplied in one of three ways, as illustrated:-
Way 1
lpApplicationName = "\path\to\executable"
lpCommandLine = "args for the executable"
which results in a process initiated with the command:
\path\to\executable args for the executable
Way 2
lpApplicationName = NULL
lpCommandLine = "\path\to\executable args for the executable"
which results in the same process as Way 1
Way 3
lpApplicationName = "\path\to\executable"
lpCommandLine = NULL
which results in a process initiated with the command \path\to\executable.
You are not using Way 1 or Way 2 or Way 3, but:
lpApplicationName = "\path\to\executable"
lpCommandLine = "\path\to\executable args for the executable"
which in your case results in a process initiated with the command:
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \
C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \
http://www.google.com/search?q=Stack+Overflow+Site"
in which the arguments passed to Chrome are:
C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe \
http://www.google.com/search?q=Stack+Overflow+Site"
Of course this does not have the anticipated outcome.
Should you wish to correct this by adopting Way 2, bear particularly
in mind what the documentation says about lpCommandLine:
...
If lpApplicationName is NULL, the first white space–delimited token of the command line specifies the module name.
...
I'm try to run program with this code:
PROCESS_INFORMATION ProcInfo = { 0 };
STARTUPINFO StartInfo = { 0 };
StartInfo.cb = sizeof(StartInfo);
if (!::CreateProcessW(NULL, (LPWSTR)wszPathToFile, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &StartInfo, &ProcInfo)) {
return GetLastError();
}
But I get error message: The system cannot find the path specified.
wszPathToFile - path to file (example: "C:\test\test.exe /retest"). Folder "test" is hidden
How to fix it?
That the folder is hidden is not relevant. That has no impact here.
As discussed in the comments, the fact that you are casting the lpCommandLine argument indicates that szPathToFile is not the correct type. It must be a pointer to a modifiable array of wide characters. If it was then you could omit the cast and the compiler would accept szPathToFile directly.
Most likely szPathToFile is actually a pointer to an array of ANSI encoded 8 bit char.
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 -------+