12

I am trying to use CreateProcess to start a new environment block and run a batch file in the new environment block. I've read through the msdn example for CreateProcess, and came up with the code shown below.

What is happening, it will open the new command prompt, and stop there. It will not run my .bat file for some reason. Using system("CALL path") will call the .bat file.

#include <iostream>

#define WINDOWS_LEAN_AND_MEAN
#include <Windows.h>

#include <strsafe.h>

#define BUFSIZE 4096

int main()
{
    //system("CALL C:\\HFSS\\setup_vars.bat");

    //return 0;

    LPWCH chNewEnv;
    LPTSTR lpszCurrentVariable;
    DWORD dwFlags = 0;
    TCHAR szAppName[] = TEXT("C:\\windows\\system32\\cmd.exe");
    TCHAR cmdArgs[] = TEXT("C:\\HFSS\\setup_var.bat");

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    BOOL fSuccess;

    // Copy environment strings into an environment block. 
    chNewEnv = GetEnvironmentStrings();

    lpszCurrentVariable = (LPTSTR)chNewEnv;
    if (FAILED(StringCchCopy(lpszCurrentVariable, BUFSIZE, TEXT("MySetting=A"))))
    {
        printf("String copy failed\n");
        return FALSE;
    }

    lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
    if (FAILED(StringCchCopy(lpszCurrentVariable, BUFSIZE, TEXT("MyVersion=2"))))
    {
        printf("String copy failed\n");
        return FALSE;
    }

    // Terminate the block with a NULL byte. 

    lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
    *lpszCurrentVariable = (TCHAR)0;

    // Create the child process, specifying a new environment block. 

    SecureZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);

#ifdef UNICODE
    dwFlags = CREATE_UNICODE_ENVIRONMENT;
#endif

    fSuccess = CreateProcess(szAppName, cmdArgs, NULL, NULL, TRUE, dwFlags,
        (LPVOID)chNewEnv,   // new environment block
        NULL, &si, &pi);

    if (!fSuccess)
    {
        printf("CreateProcess failed (%d)\n", GetLastError());
        return FALSE;
    }

    std::cout << "In new environment\n";
    WaitForSingleObject(pi.hProcess, INFINITE);

    return TRUE;
}
user2970916
  • 1,146
  • 3
  • 15
  • 37
  • On what line does it trigger a break point? The lines you're not sure of are trying to add two new environmental variables to the environment block of the process about to be launched, and you've indicated that's what you're trying to do, so I'm not sure what your question about them might be. – Ken White Sep 18 '14 at 18:11
  • I am actually trying to set the environment variables in the batch file. However, by the time I get to the batch file (without creating the new process), the %PATH% variable has all the tree data from visual studio. So, when I try to append the new path to the %PATH% variable, it is including redundant and excess information. Also, I found the crash was due to using "\" instead of "\\" – user2970916 Sep 18 '14 at 18:21
  • Your edit does not address all the points I raised. – David Heffernan Sep 18 '14 at 18:38
  • 2
    If you want only system environment variables, you can call [`CreateEnvironmentBlock`](http://msdn.microsoft.com/en-us/library/windows/desktop/bb762270%28v=vs.85%29.aspx) with `hToken` as `NULL` and `bInherit` as `FALSE`. – Eryk Sun Sep 18 '14 at 19:42

1 Answers1

21

Some problems:

  1. You need to pass the /C option to cmd.exe in order to make it execute the .bat file.
  2. The second parameter to CreateProcess must be a modifiable string. Not a literal.
  3. You need to escape backslash characters in literals.
  4. lpszCurrentVariable points to the buffer returned by GetEnvironmentStrings. You cannot modify that buffer. You need to allocate a new buffer of sufficient length and copy the environment into it. Then add your modifications.
  5. Environment blocks are double null terminated. Standard string functions are of no use with double null terminated strings.
  6. Using functions like StringCchCopy rather than C runtime functions is just confusing. Don't take MSDN example code as being the paragon of style.
  7. C strings are a bind to work with. But you use C++ so use std::wstring and other standard library classes and function.
  8. You need to define WINDOWS_LEAN_AND_MEAN before importing Windows.h.
  9. For C++, int main(void) is incorrect. The no argument main is int main().

The following code shows you how to do this:

#include <cstring>
#include <string>
#include <iostream>

#define WINDOWS_LEAN_AND_MEAN
#include <Windows.h>

std::wstring GetEnvString()
{
    wchar_t* env = GetEnvironmentStrings();
    if (!env)
        abort();
    const wchar_t* var = env;
    size_t totallen = 0;
    size_t len;
    while ((len = wcslen(var)) > 0)
    {
        totallen += len + 1;
        var += len + 1;
    }
    std::wstring result(env, totallen);
    FreeEnvironmentStrings(env);
    return result;
}

int main()
{
    std::wstring env = GetEnvString();
    env += L"myvar=boo";
    env.push_back('\0'); // somewhat awkward way to embed a null-terminator

    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;

    wchar_t cmdline[] = L"cmd.exe /C C:\\Desktop\\MyBatFile.bat";

    if (!CreateProcess(NULL, cmdline, NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT,
        (LPVOID)env.c_str(), NULL, &si, &pi))
    {
        std::cout << GetLastError();
        abort();
    }

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks for all the help David, however, running that still gives me my original problem: My batch file reads %PATH% variable as a local copy (coming from Visual Studio). Here's a link to the original problem: http://stackoverflow.com/questions/25917796/setting-path-variable-is-not-working – user2970916 Sep 18 '14 at 19:15
  • Works fine here. What's more you'll observe that the code I show is way simpler than yours and shows you how to avoid all the horrid C boilerplate. – David Heffernan Sep 18 '14 at 19:15
  • Anyway, start with my exact program. Substitute the .bat file name with a valid file name on your system. In your .bat file place the `set` command which lists environment vars. What happens when you do that? – David Heffernan Sep 18 '14 at 19:19
  • I see the PATH variable and at the end it has 3x the Path I want to add, and then a Visual Studio path to AMD64 and the Visual Studio 12.0 folder itself. – user2970916 Sep 18 '14 at 19:22
  • So my program runs fine and displays the modified environment? – David Heffernan Sep 18 '14 at 19:22
  • Yeah. It's running the same as if I were to use system("CALL /C C:\\boo.bat"). Perhaps in my .bat file, there is a way to access the system path variable and not the one that becomes modified by visual studio. – user2970916 Sep 18 '14 at 19:24
  • I'm pretty sure I answered the question that you asked here. I'd be disappointed if you expected me to now answer a new question. – David Heffernan Sep 18 '14 at 19:25
  • One thing you could do would be to modify `GetEnvString` to return `std::vector`, a list of all the distinct variables. Then find the `PATH` variable, and modify it. Then reconstitute the double null terminated environment string. – David Heffernan Sep 18 '14 at 19:27
  • Yes you answered the question. This question was created from http://stackoverflow.com/questions/25917796/setting-path-variable-is-not-working that question. – user2970916 Sep 18 '14 at 19:31
  • We answer the questions that are asked. I answered this one I think. This question makes no mention of modifications to `PATH`. How is this not an answer to your question? – David Heffernan Sep 18 '14 at 19:32
  • I agree you answered this question, but my problem from this question still remains. http://stackoverflow.com/questions/25917796/setting-path-variable-is-not-working – user2970916 Sep 18 '14 at 19:41
  • You accept the best answers to your questions and ask new ones for new problems. The code in my answer gets you on your way. I still think you haven't asked the right question yet. I still can't work out why you don't like the env vars that you get – David Heffernan Sep 18 '14 at 19:53
  • @eryksun the docs say *To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.* – David Heffernan Sep 18 '14 at 20:00
  • Hey, thanks for all the help. I was looking for that CreateEnvironmentBlock() function that allows me to use system variables over the local variables. The issue was, in my batch file, I was appending the path variable. To do that, I was accessing %PATH% variable. However, since the batch file was called from visual studios, the %PATH% variable that the .bat file was reading was different from what was set (it was reading the local version (the one I got from set)). – user2970916 Sep 18 '14 at 20:09
  • When passing `nullptr` for the first argument of `CreateProcess`, the path of the batch file (enclosed in double quotation marks) can be specified as the 2nd argument for `CreateProcess`, without the need to specify path of the command interpreter. https://stackoverflow.com/a/40655626/7571258 – zett42 Oct 31 '19 at 13:58