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>
// 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.
I’m apparently going for a record, trying to see if I can spend the entire month of October jet-lagged. So far, so good (just got back from Singapore)…
Anyway, I like free stuff as much as far more than the next guy. So, I figured I’d pass this one along – an initiative sponsored by my friend Fumie:
Windows Application Compatibility Engagement (ACE)
Concerned about application compatibility but don’t know what it takes?
Do your critical applications seem incompatible with Vista?
Sign up for ACE!
The Application Compatibility Engagement (ACE) program is a 2-3 day fully funded (FREE) on-site consultative engagement for customers who have 100+ PCs, who are considering Vista Deployment, and/or have issues with application compatibility. One of Microsoft’s ACE partners will go to your site and conduct a full assessment and develop concrete steps toward remediating any incompatible applications.
Once you participate in ACE you would have an opportunity to participate in a Case Study and have it placed on microsoft.com.
One of the main uses for my blog is to share those little annoyances that I spend hours or days solving and spare you the “fun” of going through this yourself. So, even though this isn’t really about application compatibility, which has kind of become the main theme here, it will still hopefully help save somebody some time (thanks to search engines).
I recently picked up a new laptop (Lenovo T61p, FWIW) and got everything set up according to my typical usage scenarios. I was then going about building some code, and discovered that in the transition, several of my builds had broken.
Weird, I thought. We recently moved some MFC updates and the addition of TR1 extensions to C++ from a separate feature pack into Visual Studio 2008 SP1. I was getting all of the MFC extension bits. I was missing some of the TR1 extension bits. I indexed all of the header files, and they just plain weren’t there. The shared_ptr class, for example, lived in zero header files on my hard drive.
What happened?
The fix, of course, is to just re-install Visual Studio 2008 SP1. But what broke it?
It turns out that the Windows SDK lays down files not only in C:\Program Files\Microsoft SDKs\Windows\v6.1\Include, but takes the liberty of laying down (older) files in C:\Program Files\Microsoft Visual Studio 9.0\VC\include. Yep – it up and clobbered the SP1 header files and broke my builds.
So, if you install the Windows Vista SP1 / Windows Server 2008 SDK, you may want to re-install VS2008 SP1 afterwards…