How can I launch an unelevated process from my elevated process, redux

Raymond Chen


Some time ago, I showed how you can launch an unelevated process from an elevated process by asking Explorer to launch the program on your behalf.

There’s another way which is a bit more direct, but it assumes that the thing you want to do can be done with a direct Create­Process call. In other words, if you need the system to look up the user’s file associations or default browser, then this technique is not for you.

The idea is to take advantage of PROCESS_CREATE_PROCESS access and the accompanying PROC_THREAD_ATTRIBUTE_PARENT_PROCESS process thread attribute:


The lpValue parameter is a pointer to a handle to a process to use instead of the calling process as the parent for the process being created. The process to use must have the PROCESS_CREATE_PROCESS access right.

Attributes inherited from the specified process include handles, the device map, processor affinity, priority, quotas, the process token, and job object. (Note that some attributes such as the debug port will come from the creating process, not the process specified by this handle.)

Basically, this lets you tell the Create­Process function, “Hey, like, um, pretend that other guy over there is creating the process.”

Here’s a Little Program to demonstrate. Remember that Little Programs do little to no error checking so that they can demonstrate the underlying technique without distractions.

int main(int, char**)
  HWND hwnd = GetShellWindow();

  DWORD pid;
  GetWindowThreadProcessId(hwnd, &pid);

  HANDLE process =

  SIZE_T size;
  InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
  auto p = (PPROC_THREAD_ATTRIBUTE_LIST)new char[size];

  InitializeProcThreadAttributeList(p, 1, 0, &size);
  UpdateProcThreadAttribute(p, 0,
    &process, sizeof(process),
    nullptr, nullptr);

  wchar_t cmd[] = L"C:\\Windows\\System32\\cmd.exe";
  STARTUPINFOEX siex = {};
  siex.lpAttributeList = p;
  siex.StartupInfo.cb = sizeof(siex);

  CreateProcessW(cmd, cmd, nullptr, nullptr, FALSE,
    nullptr, nullptr, &siex.StartupInfo, &pi);

  delete[] (char*)p;
  return 0;

We start by getting the shell window and using that to identify the process that is responsible for the shell.

We then use that process ID to open the process with the magic PROCESS_CREATE_PROCESS access.

We then ask the system how much memory is required to create a PROC_THREAD_ATTRIBUTE_LIST that holds one attribute, and allocate it.

We initialize the newly-allocated attribute list and update it with our process handle, saying that we want this to be the parent for the process we’re about to create.

We set up a STARTUPINFOEX structure like usual, but we take the extra step of setting the lpAttributeList to point to the attribute list.

Finally, we call Create­Process, but also set the EXTENDED_STARTUPINFO_PRESENT flag to tell it, “Hey, I gave you a STARTUPINFOEX, and if you look inside, you might find a surprise!”

A little bit of cleaning up, and we’re done.

This program runs a copy of cmd.exe using the shell process (usually explorer.exe) as its parent, which means that if the shell process is unelevated, then so too will the cmd.exe process. Of course, if the user is an administrator and has disabled UAC, then Explorer will still be elevated, and so too will be the cmd.exe. But in that case, the user wants everything to run elevated, so you’re just following the user’s preferences.

Raymond Chen
Raymond Chen

Follow Raymond   

Joshua Hudson 2019-04-25 07:18:25
Little programs do no error checking. Anybody using this needs to handle GetWindowThreadProcessId failing. If it fails you won't be able to de-elevate using this technique. I can't say for all cases but in my experience starting the child elevated is better than not at all. In the now-deleted comments of the prior thread the conclusion was the best way was to start your program non-elevated, elevate a child to do whatever (using a manifest), and having the non-elevated parent start the non-elevated child. We may improve as follows: pass your own pid as a command line argument to the child being elevated and use that PID rather than the shell PID here.
W S 2019-04-25 07:22:02
"the shell process (usually explorer.exe)" What is your/the shell teams stance on 3rd-party shells? How to write a custom shell has never been documented and making a fully functioning one gets harder with each new Windows version. To make GetShellWindow and friends work correctly you have to call undocumented functions etc.
Henrik Andersson 2019-04-25 07:43:17
So that's what the PROCESS_CREATE_PROCESS permission is for! The documentation team really needs to explain this stuff better. In particular, which process needs the permission. Or is it process handle? The documentation is a bit unclear.
Ji Luo 2019-04-25 10:23:40
I always wondered if you could use Task Scheduler to start a process as the login user. (I also wonder what happens if the user has killed Explorer and started an elevated instance of it, or even worse as someone else, in which case asking the shell to start a program doesn’t unelevate the process.)
Михаил Макаров 2019-04-25 12:56:25
Hi Raymond, I know you sometimes write entries about VS, compilers etc, so I wondered if this story could be interesting for you. Check the 2nd answer from Merrie McGaw. I can hardly express without swear words all the emotions this story sparks in me. IMHO, this is beyond the definition of absurd.
Piotr Siódmak 2019-04-25 13:41:54
You're using CreateProcessW, while passing STARTUPINFOEX, which I presume is defined as _STARTUPINFOEXA in your case since your int main takes char instead of wchar_t. Is that something to be worried about? Yay unicode!
Joshua Schaeffer 2019-04-27 18:48:10
Going back to the spirit of the blog entry that led to this, is there a way we can just use the session token to create the new process exactly like an unelevated user? The only deterministic way of using the idea on this page is to always start a process chain with an unelevated stub zombie process so that you always have it available. Then inherit the full-access process handle to the "real" process that actually does the work so that you're guaranteed to have a secure working handle without a risky OpenProcess call.
Heiko Wetzel 2019-04-29 20:29:23
As I saw your article I was very excited, because I'm looking for a working way to launch an unelevated process from an elevated process already for a long time. I used your code (enhanced with error checking) and was able to spawn an unelevated process. That's great. But I made the observation, that the unelevated process runs in a compatibility mode, the environment variable __COMPAT_LAYER in the unelevated process has the value "Installer". Is there a way to prevent enabling the compatibility mode (for the new process) or can you explain, why the compatibility mode ist applied in this scenario? As I don't know about the impact of the compatibility mode on the process, I can't decide if it is safe to ignore it. Thanks 
Gavin Lambert 2019-05-05 18:12:13
There's a big caveat to this technique.  If the user has used "runas" or CreateProcessAsUser or similar to launch your process (or some parent of your process) under a different user account from the shell, then this would probably either fail with a permissions error or work but launch the child process under the original user account (which might annoy the actual user, since they presumably had a reason to switch user accounts in the first place).  There also might be security problems if you then try to communicate between the two processes. Like Joshua Hudson stated, I've found that the best practice is to launch unelevated, then launch an elevated child that communicates back to its still-not-elevated parent if need be, rather than trying the reverse.  Although that can still be defeated by "runas", in this case if the user chooses to run the original process as administrator -- but that probably falls into the "doing what the user requested" bucket, so it's still better than the above. Otherwise, the only other solution that seems safe is if Windows provided an API to acquire the corresponding non-elevated filtered token from a process's current token, and then use CreateProcessAsUser to launch unelevated.  (It is probably currently possible to perform a combination of existing API calls to recreate a new filtered token, but since AFAIK the difference between the elevated and unelevated tokens is not entirely specified, that seems less safe than Windows providing an explicit API to do it.)  Although as Raymond pointed out in the original blog post, this is not necessarily correct either, if elevation occurred with credentials. Another possibility (if you don't want an unelevated launchpad process) is to get your parent process's token (at startup), then later use CreateProcessAsUser with that token to launch the unelevated process.  (Caveat: I haven't checked if this actually works -- there are probably cross-user permissions issues -- and there's a race condition if your parent process terminates before you have a chance to capture its token.  It's also a bit tricky to get your parent process without its cooperation, which makes the launchpad process more attractive anyway.)