Welcome to MSDN Blogs Sign in | Join | Help

News

  • Chris Jackson is a Principal Consultant at Microsoft and the Technical Lead for the Windows Application Experience SWAT Team. But most people just call him The App Compat Guy.

    This is provided "AS IS" with no warranties, and confers no rights. Use of materials found on this page is subject to the terms specified in the Terms of Use

Why don’t elevated applications receive environment variables set by non-elevated calling process?

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.

Posted: Wednesday, October 29, 2008 11:00 AM by Chris Jackson

Comments

Chris Corio said:

If I recall correctly, we also decided to set the current working directory to %windir%\system32 for elevated processes.  I'm pretty sure this was for every elevated process but I can't recall whether there was optional logic in the App Info Service.  This behavior might come into play if you decided to do your IPC using files.

# October 31, 2008 8:37 PM

Chris Jackson said:

Yes, we do set the working directory to system32 when we elevate, but that's orthogonal to what we do with the environment block...

# November 1, 2008 1:12 AM
New Comments to this post are disabled
Page view tracker