Why don’t elevated applications receive environment variables set by non-elevated calling process?
Published Aug 14 2018 04:05 PM 215 Views
Microsoft
First posted to MSDN on Oct, 29 2008

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.

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.

Version history
Last update:
‎Nov 13 2018 08:08 AM
Updated by: