I had a conversation with a customer (via email) the other day, and I wanted to to into a bit of detail here explaining what is going on.

Essentially, the customer was attempting to pass information to another application while launching it using environment variables, and it wasn’t working. Of course, it used to work, and it was confusing that it didn’t. Particularly since this failure seems to contradict the following statement directly from the SDK: “By default, a child process inherits a copy of the environment block of the parent process.”

The launching process was not elevated, but the target process was.

I recommended using command lines instead, but wanted to illustrate what is happening here. To see it for yourself, you can create the following two programs:

elevation_launcher.cpp

#include <windows.h>
#include <shellapi.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {

    UNREFERENCED_PARAMETER(hInstance);
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    UNREFERENCED_PARAMETER(nShowCmd);

    // Configure the process to launch
    SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };
    sei.lpFile = TEXT("Elevation Target.exe");
    sei.nShow = SW_SHOWNORMAL;

    // Attempt to pass data using environment variables
    SetEnvironmentVariable(TEXT("FromParent"), TEXT("Passed using environment variable"));

    // Attempt to pass data using the command line
    TCHAR szCommandLine[] = TEXT("\"Passed using command line\"");
    sei.lpParameters = szCommandLine;

    // Launch the child app
    if (!ShellExecuteEx(&sei)) {
        DWORD dwStatus = GetLastError();
        // ... handle the error
    } else {
        // ... handle the success
    }
    return 0;
}

elevation_target.cpp

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {

    UNREFERENCED_PARAMETER(hInstance);
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    UNREFERENCED_PARAMETER(nShowCmd);

    // Retrieve and display the parameter passed using environment variables
    PTSTR pszValue = NULL;
    DWORD dwResult = GetEnvironmentVariable(TEXT("FromParent"), pszValue, 0);
    if (dwResult != 0) {
        DWORD size = dwResult * sizeof(TCHAR);
        pszValue = (PTSTR)malloc(size);
        GetEnvironmentVariable(TEXT("FromParent"), pszValue, size);
        MessageBox(NULL, pszValue, TEXT("Environment Variable"), MB_OK | MB_ICONINFORMATION);
        free(pszValue);
    } else {
        MessageBox(NULL, TEXT("The environment variable was not found"), TEXT("Environment Variable"), MB_OK | MB_ICONERROR);
    }

    // Retrieve and display the parameter passed using the command line
    int nNumArgs;
    PWSTR *ppArgv = CommandLineToArgvW(GetCommandLine(), &nNumArgs);
    if (nNumArgs > 1) {
        MessageBox(NULL, ppArgv[1], TEXT("Command Line"), MB_OK | MB_ICONINFORMATION);
    } else {
        MessageBox(NULL, TEXT("The command line was not found"), TEXT("Command Line"), MB_OK | MB_ICONERROR);
    }
    HeapFree(GetProcessHeap(), 0, ppArgv);

    return 0;

}

Now, if you manifest both files with an asInvoker reference, both pieces of data are sent to the child process – the environment variable, and the command line. However, if you manifest elevation_target as requireAdministrator, leaving elevation_launcher as asInvoker, you still get the command line, but you lose the environment variable.

Huh?

To understand what is going on, you have to understand that, when you elevate, the application, you aren’t actually the parent. Rather, the shell calls into the Application Information Service. This service calls consent.exe, which is what prompts for elevation. Assuming the request is approved, the service then uses the linked elevated token and calls CreateProcessAsUser using the linked token.

image

So, the Application Information Service is the parent of the elevated process, not the process that called ShellExecute(Ex). And the elevated process inherits that environment block.

Of course, it confuses things somewhat that we then reparent the process so it looks like the launching process is the actual parent if you look at the process tree using a tool such as Process Explorer.