Cannot start process from different directory - c++

I'm trying to write a program in C++ that launches an executable. When I try to run the program, nothing happens. But when I try to run the program on the same directory and drive the exe is in, it does work.
Here's the code:
else if (result1 == 0) {
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);
CreateProcess(L"D:\\Games\\Origin Games\\Command and Conquer Red Alert\\RA95Launcher.exe", NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
When I do it like this:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);
CreateProcess(L"D:\\Games\\Origin Games\\Command and Conquer Red Alert\\RA95Launcher.exe", NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, "D:\\Games\\Origin Games\\Command and Conquer Red Alert\\", &si, &pi);
I get the error:
argument of type "const char *" is incompatible with parameter of type "LPCWSTR"
Any help would be appreciated.

Most likely you should set the lpCurrentDirectory parameter to the directory the exe is in.
See: CreateProcess
It's often that executables like RA95Launcher.exe are depending on other files. Usually these are reffered to by a relative path from perspective of the Current Working Directory.
If you launch the process from within the directory the exe is in, your automatically in the correct Current Working Directory. If you have the launcher running at a different location, you'll need to specify it.

I get the error: argument of type "const char *" is incompatible with
parameter of type "LPCWSTR"
This means that you're trying to pass incompatible type to function. LPCWSTR is Windows.h typedef for const wchar_t*(a UTF-16 string), but CreateProcess requires ANSI string(LPCSTR). You can either call the appropriate function, in this case - CreateProcessW(W at the end of the function name means it takes UTF-16 strings as parameters) or make your string ANSI by removing L before it.

Related

Executing rundll32.exe with CreateProcess

I've created a DLL and would like to execute one of the functions using the rundll32.exe command on windows.
Using rundll32.exe, it runs correctly from the command line; however, I'd like to call it (rundll32.exe) from a separate program. I cannot directly call the function from my code due to 32/64 bit compatibility issues in the underlying libraries I'm using (Easyhook).
Below is what I'm using in an attempt to run the dll function:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi));
LPCTSTR application = "C:\\Windows\\system32\\rundll32.exe";
LPTSTR cmd = "C:\\Projects\\Test\\mydll.dll,MyFunc";
BOOL cpRes = CreateProcess(application,
cmd,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi);
if(cpRes == 0) {
cout << "ERROR\n";
cout << GetLastError() << endl;
} else {
cout << "DLL Launched!" << endl;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
The output to my console is always DLL Launched; however, I do not see the effects of my DLL actually being called (currently stubbed out in such a way that the command writes to a file).
If I swap out the application with something such as C:\\Windows\\system32\\notepad.exe, the program successfully runs.
For completion, here's the body of MyFunc:
ofstream file;
file.open("C:\\Projects\\Test\\test.txt");
file << "I wrote to a file!";
file.close();
Is there any reason CreateProcess cannot be used with rundll32? While reading over this I found several warnings about LoadLibrary() and DLLMain but it doesn't seem like they're relevant to this.
More Clarification:
This is currently a 32-bit application (allegedly) launching the 32-bit rundll32.exe (Logic will be added later to call the 32 or 64 bit version).
My dll is as follows:
extern "C" __declspec(dllexport) void CALLBACK MyFunc(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);
void CALLBACK MyFunc(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) { ... }
Which also has a .def file with:
EXPORTS
MyFunc
Running
C:\Windows\system32\rundll32.exe C:\Projects\Test\mydll.dll,MyFunc
produces the expected results.
Update
Setting application to NULL and including the rundll32.exe in cmd as mentioned in the comments seems to work.
Relevant Docs:
CreateProcess
RunDll32.exe
Per 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.
You are not repeating rundll32.exe as the first command-line token.
So, if you continue using the lpApplicationName parameter, then change this:
LPCTSTR application = "C:\\Windows\\system32\\rundll32.exe";
LPTSTR cmd = "C:\\Projects\\Test\\mydll.dll,MyFunc";
To this instead:
LPCTSTR application = TEXT("C:\\Windows\\system32\\rundll32.exe");
LPTSTR cmd = TEXT("C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc");
Note that you are currently compiling for ANSI/MBCS (by virtue of the fact that you are passing narrow strings to CreateProcess()). If you ever update the project to compile for Unicode, use this instead:
TCHAR cmd[] = TEXT("C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc");
This is because the documentation states:
lpCommandLine [in, out, optional]
...
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 might consider changing cmd into a TCHAR[] array anyway, even in ANSI/MBCS, so you can do something like this:
LPCTSTR application = TEXT("C:\\Windows\\system32\\rundll32.exe");
TCHAR cmd[(MAX_PATH*2)+10];
wsprintf(cmd, TEXT("%s %s,%s"), application, TEXT("C:\\Projects\\Test\\mydll.dll"), TEXT("MyFunc"));
Either way, by passing the module filename as the first token in the lpCommandLine parameter, you can then set the lpApplicationName parameter to NULL:
The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space–delimited token in the lpCommandLine string.
Let CreateProcess() setup the correct command-line to pass to rundll32.exe for you:
TCHAR cmd[] = TEXT("C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc");
BOOL cpRes = CreateProcess(NULL, cmd, ...);

Command Line Argument Truncated by CreateprocessW

VS10, MBCS, Unicode preprocessor defs.
Having gone through Dr Google's casebook, the best help was here, but it doesn't quite address this particular problem. Consider this code where thisexePath is the path to the executable:
cmdLineArg = (wchar_t *)calloc(BIGGERTHANMAXPATH, sizeof(wchar_t));
wcscpy_s (cmdLineArg, maxPathFolder, L"\\\\??\\C:\\My directory\\My directory\\");
CreateProcessW (thisexePath, cmdLineArg, NULL, NULL, FALSE, NULL, NULL, NULL, &lpStartupInfo, &lpProcessInfo)
When the program is called
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
wchar_t hrtext[256];
swprintf_s(hrtext, sizeof(hrtext), L"%ls", lpCmdLine);
//deal with possible buffer overflow later
MessageBoxW(NULL, hrtext, L"Display", MB_ICONINFORMATION);
}
hrtext only displays "My directory\My directory\" Something obvious missed here?
This is not correct:
wchar_t hrtext[256];
swprintf_s(hrtext, sizeof(hrtext), L"%ls", lpCmdLine);
The second parameter should denote the number of characters, not bytes.
MSDN link to swprintf_s
It should be this:
wchar_t hrtext[256];
swprintf_s(hrtext, sizeof(hrtext) / sizeof(wchar_t), L"%ls", lpCmdLine);
or simply:
wchar_t hrtext[256];
swprintf_s(hrtext, 256, L"%ls", lpCmdLine);

C++ process class error

Following the tutorial here, I decided to make a process class for C++ so that I did not have to constantly keep writing out the same code for starting a process. It does work starting the process, but when I pass a command line function, it does nothing. Example ("c:\\windows\\notepad.exe", "c:\\windows\\PFRO.txt"). What is the problem?
Note: format is just a basic formatting function, using vsprintf
class process
{
public:
static BOOL __stdcall start(LPCSTR _Proc_name, LPSTR _Command_line = NULL, LPSECURITY_ATTRIBUTES _Proc_attrib = NULL,
LPSECURITY_ATTRIBUTES _Thread_attrib = NULL, BOOL _Inherits_handles = FALSE, DWORD _Creation_flags = NULL,
LPVOID _Environment = NULL, LPCSTR _Cur_directory = NULL)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(_Proc_name, _Command_line, _Proc_attrib, _Thread_attrib,
_Inherits_handles, _Creation_flags, _Environment, _Cur_directory, &si, &pi))
{
fputs(format("process::start(...) failed [%d]\n", GetLastError()), stderr);
return false;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
};
int main()
{
process::start("c:\\windows\\notepad.exe", "c:\\windows\\PFRO.txt");
getchar();
}
When the command line parameter is parsed to provide arguments for a main function, the first token is taken to be the executable file. A called program might well try to open the second token as its file argument, and of course you there wasn't one.
The usual practice is to repeat the program name as the first token in the command line. For example
process::start("c:\\windows\\notepad.exe", "notepad c:\\windows\\PFRO.txt");

What should the second parameter of CreateProcess be?

I am trying to start a server using CreateProcess(). Here is the Code:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
int result;
STARTUPINFO si;
PROCESS_INFORMATION pi;
CreateProcess("C:\\AP\\DatabaseBase\\dbntsrv.exe", "*** WHAT SHOULD I PUT HERE***", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
return 0;
}
I did not understand from the documentation what the 2nd parameter should be. Can you please help me with it?
Thank You
From MSDN:
lpCommandLine [in, out, optional]
The command line to be executed. The maximum length of this string is 32,768 characters, including the Unicode terminating null
character. If lpApplicationName is NULL, the module name portion of
lpCommandLine is limited to MAX_PATH characters.
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.
The lpCommandLine parameter can be NULL. In that case, the function uses the string pointed to by lpApplicationName as the
command line.
So NULL is OK there, at least. As soon as you don't pass arguments.
You use it to pass arguments to the .exe defined by the first parameter:
An example would be to call the cmd.exe and then run a script or use a zip utility:
void runCmd(const tstring& cmdString, STARTUPINFO &si, PROCESS_INFORMATION &pi)
{
ZeroMemory( &si, sizeof(si) );
ZeroMemory( &pi, sizeof(pi) );
si.cb = sizeof(si);
tstring cmd_exe_path(win_dir);
cmd_exe_path.append( _T("\\System32\\") ).append(CMD_PROCESS);
tstring argline( _T("/c ") );
argline += cmdString;
tstring curr_dir( cmdString.substr( 0, cmdString.rfind( _T('.') ) ) );
curr_dir.erase( curr_dir.find_last_of( _T("/\\") ) );
size_t pos = curr_dir.find( _T("\"") );
while( pos != tstring::npos )
{
curr_dir.erase( pos, pos + 1 );
pos = curr_dir.find( _T("\"") );
}
//USE FULL PATHS FOR SAFETY... Include wrapping quotes if spaces required in path
LOG(LOG_INFO,_T("runCmd(): Calling %s %s Dir[ %s ]"),cmd_exe_path.c_str(),argline.c_str(), curr_dir.c_str());
if( !CreateProcess( cmd_exe_path.c_str(), &argline[0], NULL, NULL, FALSE, CREATE_NEW_CONSOLE,
NULL,curr_dir.c_str(),&si,&pi ) ) //this generates warning C6335 - resource leak... however handles should be closed by owner
{
DWORD dw = GetLastError();
std::string error( "runCmd(): Failed to create Shutdown process - error code is " );
error.append(boost::lexical_cast<std::string>(dw));
LOG(LOG_INFO,error.c_str());
throw std::exception(error.c_str());
}
LOG(LOG_INFO,"runCmd(): process starting with PID[%d] TID[%d]",pi.dwProcessId,pi.dwThreadId);
}

How to ensure only one process is created by CreateProcess when calling concurrently in c++?

Quoted from here:
BOOL WINAPI CreateProcess(
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
I have two independant programe that creates exactly the same process, how can I ensure that if one of them has already created the process, the other won't create it twice?
The most simple way is if you create a named object after the start of the program. For example CreateEvent, CreateMutex and so on. To verify existance of the application you can just use OpenEvent, OpenMutex and so on before creating of the object. You can choose (if desired) the name of the object with the the "Global\" prefix (see http://msdn.microsoft.com/en-us/library/aa382954.aspx) to allow only one process for all terminal server session.
UPDATED: Because how I can see there are different opinions about my suggestion I try to explain it more exactly and add the corresponding test example.
The main idea is that the application which are started create any named object is the object with the same name not yet exist. This only reserve the name in the Kernel Object Namespaces. No real usage of the object are needed. The advantaged of this way compared with creating of a file on the disk is that named objects are temporary and are owned by a application. So if the application are ended, be killed or be terminated in any other way (because of unhanded exception for example) the named object will be automatically deleted by the operation system. In the following example I don't use CloseHandle at all. How you can test the application can successfully determine whether it runs as the first instance or not.
#include <windows.h>
//#include <Sddl.h>
LPCTSTR g_pszEventName = TEXT("MyTestEvent"); // TEXT("Global\\MyTestEvent")
void DisplayFirstInstanceStartedMessage()
{
TCHAR szText[1024];
wsprintf (szText,
TEXT("The first instance are started.\nThe event with the name \"%s\" is created."),
g_pszEventName);
MessageBox (NULL,
szText,
TEXT("CreateEventTest"), MB_OK);
}
void DisplayAlreadyRunningMessage ()
{
TCHAR szText[1024];
wsprintf (szText,
TEXT("The first instance of the aplication is already running.\nThe event with the name \"%s\" already exist."),
g_pszEventName);
MessageBox (NULL,
szText,
TEXT("CreateEventTest"), MB_ICONWARNING | MB_OK);
}
void DisplayErrorMessage (DWORD dwErrorCode)
{
if (dwErrorCode == ERROR_ALREADY_EXISTS)
DisplayAlreadyRunningMessage();
else {
LPTSTR pErrorString;
if (FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | // Always search in system message table !!!
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS |
0, NULL, // source of message definition
dwErrorCode, // message ID
// 0, // language ID
// GetUserDefaultLangID(), // language ID
// GetSystemDefaultLangID(),
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pErrorString, // pointer for buffer to allocate
0, // min number of chars to allocate
NULL)) {
MessageBox (NULL, pErrorString, TEXT("CreateEventTest"), MB_OK);
LocalFree (pErrorString);
}
else {
TCHAR szText[1024];
wsprintf (szText, TEXT("Error %d in the CreateEvent(..., \"%s\")"), dwErrorCode, g_pszEventName);
MessageBox (NULL, szText, TEXT("CreateEventTest"), MB_OK);
}
}
}
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
//SECURITY_ATTRIBUTES sa;
//BOOL bSuccess;
HANDLE hEvent = OpenEvent (EVENT_MODIFY_STATE, FALSE, g_pszEventName);// EVENT_ALL_ACCESS
if (hEvent == NULL) {
DWORD dwErrorCode = GetLastError();
if (dwErrorCode != ERROR_FILE_NOT_FOUND) {
DisplayErrorMessage(dwErrorCode);
return 1;
}
}
else {
DisplayAlreadyRunningMessage();
return 0;
}
//sa.bInheritHandle = FALSE;
//sa.nLength = sizeof(SECURITY_ATTRIBUTES);
//bSuccess = ConvertStringSecurityDescriptorToSecurityDescriptor (
// TEXT("D:(A;OICI;GA;;;WD)"), // Allow full control
// SDDL_REVISION_1,
// &sa.lpSecurityDescriptor,
// NULL);
hEvent = CreateEvent (NULL, // &sa
TRUE, FALSE, g_pszEventName);
//sa.lpSecurityDescriptor = LocalFree (sa.lpSecurityDescriptor);
if (hEvent == NULL) {
DWORD dwErrorCode = GetLastError();
DisplayErrorMessage(dwErrorCode);
return 1;
}
else
DisplayFirstInstanceStartedMessage();
return 0;
UNREFERENCED_PARAMETER (hInstance);
UNREFERENCED_PARAMETER (hPrevInstance);
UNREFERENCED_PARAMETER (lpCmdLine);
UNREFERENCED_PARAMETER (nShowCmd);
}
If one want support that different users from the same desktop or from the different desktops could start only one instance of the program, one can uncomment some parts of the commented code or replace the name MyTestEvent of the event to Global\MyTestEvent.
I hope after the example my position will be clear. In such kind of the event usage no call of WaitForSingleObject() are needed.
You cannot do this by letting the process you start creating a named object. That's an inherent race condition, it takes time for the process to get started. Both programs need to call CreateMutex at some point before trying to create the 3rd process with an agreed-upon name. Then they need to call WaitForSingleObject() with a zero wait time to try to acquire the mutex. Whomever gets it is the one that should call CreateProcess().
More work is needed after this to deal with this 3rd process terminating.
You can use this function
BOOL WINAPI EnumProcesses(
__out DWORD *pProcessIds,
__in DWORD cb,
__out DWORD *pBytesReturned
);
to get a list of all the pids of all currently running processes and check if the process is running?