How can I launch an unelevated process from my elevated process and vice versa?


Going from an unelevated process to an elevated process is easy. You can run a process with elevation by passing the runas verb to Shell­Execute or Shell­Execute­Ex.

Going the other way is trickier. For one thing, it's really hard to munge your token to remove the elevation nature properly. And for another thing, even if you could do it, it's not the right thing to do, because the unelevated user may be different from the elevated user.

Let me expand on that last bit.

Take a user who is not an administrator. When that user tries to run a program with elevation, the system will display a prompt that says, "Hey, like, since you're not an administrator, I need you to type the userid and password of somebody who is an administrator." When that happens, the elevated program is running not as the original user but as the administrative user. Even if the elevated program tried to remove elevation from its token, all it managed to do is create an unelevated token for the administrative user, not the original user.

Suppose we have Alice Administrator and Bob Banal. Bob logs on, and then tries to run LitWare Dashboard, which requires elevation. The prompt comes up, and Bob calls over Alice to grant administrative privileges. Alice types her password, and boom, now LitWare Dashboard is running elevated as Alice.

Now suppose LitWare Dashboard wants to launch the user's Web browser to show some online content. Since there is no reason for the Web browser to run elevated, it tries to unelevate the browser in order to reduce the security attack surface. If it simply neutered its token and used that to launch the browser, it would be running a copy of the browser unelevated as Alice. But LitWare Dashboard presumably really wanted to run the browser as Bob, since it is Bob who is the unelevated user in this session.

The solution here is to go back to Explorer and ask Explorer to launch the program for you. Since Explorer is running as the original unelevated user, the program (in this case, the Web browser) will run as Bob. This is also important in the case that the handler for the file you want to open runs as an in-process extension rather than as a separate process, for in that case, the attempt to unelevate would be pointless since no new process was created in the first place. (And if the handler for the file tries to communicate with an existing unelevated copy of itself, things may fail because of UIPI.)

Okay, I know that Little Programs are not supposed to have motivation, but I couldn't help myself. Enough jabber. Let's write code. (Remember that Little Programs do little or no error checking, because that's the way they roll.)

#define STRICT
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>

// FindDesktopFolderView incorporated by reference

void GetDesktopAutomationObject(REFIID riid, void **ppv)
{
 CComPtr<IShellView> spsv;
 FindDesktopFolderView(IID_PPV_ARGS(&spsv));
 CComPtr<IDispatch> spdispView;
 spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
 spdispView->QueryInterface(riid, ppv);
}

The Get­Desktop­Automation­Object function locates the desktop folder view then asks for the dispatch object for the view. We then return that dispatch object in the form requested by the caller. This dispatch object is a Shell­Folder­View, and the C++ interface for that is IShell­Folder­View­Dual, so most callers are going to ask for that interface, but if you are a masochist, you can skip the dual interface and talk directly to IDispatch.

void ShellExecuteFromExplorer(
    PCWSTR pszFile,
    PCWSTR pszParameters = nullptr,
    PCWSTR pszDirectory  = nullptr,
    PCWSTR pszOperation  = nullptr,
    int nShowCmd         = SW_SHOWNORMAL)
{
 CComPtr<IShellFolderViewDual> spFolderView;
 GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
 CComPtr<IDispatch> spdispShell;
 spFolderView->get_Application(&spdispShell);

 CComQIPtr<IShellDispatch2>(spdispShell)
    ->ShellExecute(CComBSTR(pszFile),
                   CComVariant(pszParameters ? pszParameters : L""),
                   CComVariant(pszDirectory ? pszDirectory : L""),
                   CComVariant(pszOperation ? pszOperation : L""),
                   CComVariant(nShowCmd));
}

The Shell­Execute­From­Explorer function starts by getting the desktop folder automation object. We use the desktop not because it's particularly meaningful but because we know that it's always going to be there.

As with the desktop folder view, the Shell­Folder­View object is not interesting to us for itself. It's interesting to us because the object resides in the process that is hosting the desktop view (which is the main Explorer process). From the Shell­Folder­View, we ask for the Application property so that we can get to the main Shell.Application object, which has the IShell­Dispatch interface (and its extensions IShell­Dispatch2 through IShell­Dispatch6) as its C++ interfaces. And it is the IShell­Dispatch2::Shell­Execute method that is what we really want.

"You never loved me. You only wanted me in order to get access to my family," sobbed the shell folder view.

And we call IShell­Dispatch2::Shell­Execute with the appropriate parameters. Note that the parameters to IShell­Dispatch2::Shell­Execute are in a different order from the parameters to Shell­Execute!

Okay, let's put this inside a little program.

int __cdecl wmain(int argc, wchar_t **argv)
{
 if (argc < 2) return 0;

 CCoInitialize init;
 ShellExecuteFromExplorer(
    argv[1],
    argc >= 3 ? argv[2] : L"",
    argc >= 4 ? argv[3] : L"",
    argc >= 5 ? argv[4] : L"",
    argc >= 6 ? _wtoi(argv[5]) : SW_SHOWNORMAL);

 return 0;
}

The program takes a mandatory command line argument which is the thing to execute, be it a program or a document or a URL. Optional parameters are the parameters to the thing being executed, the current directory to use, the operation to perform, and how the window should be opened.

Open an elevated command prompt, and then run this program in various ways.

scratch http://www.msn.com/ Open an unelevated Web page in the user's default Web browser.
scratch cmd.exe "" C:\Users "" 3 Open an unelevated command prompt at C:\Users, maximized.
scratch C:\Path\To\Image.bmp "" "" edit Edit a bitmap in an unelevated image editor.

This program is basically the same as the Execute in Explorer sample.

Comments (27)
  1. Brian G. says:

    I run as non-admin user with an admin account (both under my control) to do all the installing and muckity-muck. I wish installers used this more often. I get very annoyed when I install a program and the last thing it does is launch the newly-installed program as my admin user.

  2. Joshua says:

    So what do you do if explorer isn't running?

    @Brian G: Most developers good enough to figure this out hate UAC as the load fell hardest on them and they can see the uselessness of it. It is so tempting to use one of the auto-elevate holes to avoid bugging the user when elevate is required.

  3. skSdnW says:

    Elevating is not that easy either, when UAC is off the runas verb is "broken" (IMHO it should show the NT5 runas dialog but instead it just starts the process unelevated) so the child application must verify that it is actually elevated.

    This going back down example fails if Explorer is not running so you should be prepared for that and create a restricted token as best you can if you really need to start a new process. An alternative is to have a middleman which starts itself elevated and then uses some type of IPC to trigger the required unelevated task.

  4. DWalker says:

    Raymond, where can I get the ValleySpeak language pack, so every dialog box will start with "Hey, like, …"?

  5. kinokijuf says:

    @skSdnW The Flash installer does not open the browser.

  6. @kinokijuf says:

    Maybe it doesn't now, but I recall it did try to open a test page at one point, said something like thanks for installing flash, flash content should now work as on this page…

  7. BZ says:

    "We use the desktop not because it's particularly meaningful but because we know that it's always going to be there."

    This is a curious statement to make when Windows is transitioning full tilt into the Metro (yes, I know, but what do you want me to call it?) environment. Oh, I realize that the application in question is a desktop application, but even so, there could be a time when desktop apps run in some special backwards-compatible sandbox without a desktop. Or is there some guarantee somewhere that Win32 will always have a Desktop? After all, it didn't before Windows 95 / NT4, right?

    [Since this is a desktop application, there will be a desktop (though it might be merely a backward compatibility desktop). And yes, there was no desktop prior to Windows 95, but so what? No pre-Windows 95 application would try to access the desktop view since there was no such thing as a desktop view. Are you looking for the nitpicker statement "There will always be a desktop view on any version of Windows that has APIs for accessing the desktop view"? -Raymond]
  8. Random832 says:

    Re: "So what do you do if explorer isn't running?"

    Or if explorer is running elevated, for that matter? Even if you're not supposed to do so, it's not hard to do it by accident.

    I take it from the nature of the solution in this post that there's no way for the elevated app to actually directly acquire the unelevated token it was started from?

    [If explorer is running elevated, then the said "Run everything elevated!" in which case you presumably should honor the user's (poor) choice and open the Web browser elevated. (Imagine if the unelevated token were retained. That would mean that if you elevate from user B to user A, and then user B logs off, user B's token remains alive inside the elevated process, and then Bad Things Happen.) -Raymond]
  9. Joshua says:

    [Since this is a desktop application, there will be a desktop]

    I think I already knocked down that statement.

    I really do detest the number of software components I encounter that assume that Explorer is running. I tend to have to write them off as having critical defects sooner or later. There's just too many cases where explorer is not running.

    I find myself in a position where I may soon need to make a stripped-down alternate shell because Microsoft will not address accessibility bugs.

  10. skSdnW says:

    Someone should tell the authors of the ja** and fla** installers/updaters about this code example, they somehow think its a good idea to start a elevated browser session.

    To top if off, if you install both the activex and netscape plugins one of them is going to open the wrong browser…

  11. foo says:

    > I find myself in a position where I may soon need to make a stripped-down alternate shell because Microsoft will not address accessibility bugs.

    Can you hire xpclient as a UI consultant? That would be super awesome.

  12. Myria says:

    I remember you mentioning that this post was coming, and it's finally here!  Thank you!!!

    My solution had a similar effect, but was more complicated and really wasn't the best approach.  Yours is definitely better!

    My solution was to use GetShellWindow then GetWindowThreadProcessId to find the master Explorer's process ID, OpenProcess then OpenProcessToken to get Explorer's unelevated token, DuplicateTokenEx to create a new primary token, then finally CreateProcessAsUserW to run your new process.  This is not perfect, due to things like the environment not being inherited properly, but CreateEnvironmentBlock may help with that somewhat.  Supporting the cross-user scenario is also possible, but much more difficult: you have to use the fact that you're running with Administrator access to acquire SeDebugPrivilege and SeTcbPrivilege so that you can call OpenProcess and CreateProcessAsUserW on other users' objects.

  13. James says:

    This may be obvious, but why doesn't UAC create an new token for the current user and add the privileges from the administrator. I mean just because the administrator gives you permission to do something, doesn't mean suddenly you are them!

    I suppose this falls over in network scenarios.

    [The definition of "has administrative privileges" is "belongs to the Administrators group." That's how all the ACLs in the system are set up. (ACLs don't check privileges; they check SIDs.) -Raymond]
  14. Paul says:

    If it were not for the issue with the elevated process running under a different *user*, could you create an appropriately restricted token using CreateRestrictedToken()? No "munging" required.

    [Create­Restricted­Token is part of the munging. Note that the munged token is a new token, different from the token being used by Explorer. This means that you may have issues trying to share information with Explorer and other applications running under Explorer's token, like network drive mappings. -Raymond]
  15. Joshua says:

    I think Myria's solution is a better choice. BTW, you do not have to call DuplicateTokenEx. Use the existing token.

  16. Myria says:

    @Joshua: I like Raymond's solution more, because it far more easily supports the cross-user case; no SeTcbPrivilege nonsense is required.  Also, it spawns the new process the same way Explorer would, which is very much the entire point of the exercise.

    I suppose that OpenProcessToken already does return a primary token.  If you open it with the right permissions, I suppose you don't need to DuplicateTokenEx, yeah.

  17. Interesting approach :)

    I had the same problem with my custom made installer. First it starts without elevation. Once the user has made his choices the actual installer (without GUI) is extracted and run elevated (through manifest). Once the installer is done it gives control back to the main app (still running as the original user) which can now properly run the installed executable.

  18. David Trapp says:

    Still, the question remains what happens when there is no explorer.exe at all. For example, somebody might be using a different shell, such as LiteStep, or the shell was changed to this very program as "kiosk mode" for users (I know that this is not secure at all, though)…

    I wonder why there is no simple API for that. Relying on Explorer here sounds quite like a hack to me.

  19. Matt says:

    Explorer is an integral part of Windows, so relying on it isn't unreasonable (it runs your StartMenu and draws your icons for example).

    But if you're looking for a pathological case of "But what if we're running some crazy Windows where we don't have Explorer for some reason", the answer is simple. You get someone ELSE to run Explorer by asking them so you inherit their token.

    And what if nobody else is running (for instance you're running in winlogon.exe and noone else has started up yet?) Well you'll have to create a user token via LSASS, and then use that token to create the process via CreateProcessAsUser.

    But if you're NOT running in a crazy world of just NT running with Windows somehow crippled on top of it, then Raymond's way of asking Explorer to just launch the process on your behalf will save you a whole world of pain.

  20. Jan Ringoš says:

    Well, there are legitimate SKUs where explorer.exe is actually missing, e.g. Server Core or standalone Hyper-V Server, not mentioning custom configurations of Windows Embedded. But most of those have one thing in common: also no UAC.

  21. @Matt says:

    If you want a kiosk-like system where no Explorer hotkeys are available to the end user, you can officially use the registry key HKCUSoftwareMicrosoftWindowsCurrentVersionPoliciesSystemShell to run your program as the user's shell (look here: msdn.microsoft.com/…/ms815238.aspx, under "Custom user interface").

    For example, think of some terminal for truck drivers, used inside an industrial area ("intranet application"). There are systems where not absolutely everything is under control of the software vendor because this would be way to expensive, but where you still don't like the end user being able to use Task Manager or Explorer or Help or Alt+Tab.

    The easiest way to disable all this is to replace the shell.

  22. "Impersonation is fragile".

    That's what came to my mind immediately after seeing the topic. It was a blast from the past, a 2002 article by Michael Howard and Keith Brown that said in-process COM servers with different thread modeling run in different threads that do not inherit the impersonated token. (I'm not saying it is related; I'm saying that's what I remember.) Now, starting with Windows Vista, it is impossible to launch Explorer with administrative privileges. It uses DCOM Server Process Launcher Service to bypass the token. Now, I've been asking myself: Does "launch folder window in a separate process" policy do anything anymore?

    Now good installers do not start straightaway in admin mode; the bootstrapper gather user input and then request the servicing stack to do the installing (which requests elevation). The servicing stack uses the bootstrapper instead of Explorer to launch whatever is needed.

  23. BZ says:

    Re: Are you looking for the nitpicker statement "There will always be a desktop view on any version of Windows that has APIs for accessing the desktop view"

    Yes, that is exactly what I'm looking for because I was not certain that this statement is true. Thanks for clearing that up.

    [I'm kind of confused by your confusion. It appears that you expected the statement to imply that Windows 1.0 has a desktop view? -Raymond]
  24. Random832 says:

    "That would mean that if you elevate from user B to user A, and then user B logs off, user B's token remains alive inside the elevated process, and then Bad Things Happen"

    User B logs off, their session and window station go away, and the elevated program goes away with it? If you're starting a noninteractive process, the thing you linked is bad even if you don't elevate to a different user.

    Okay, what if the token doesn't stay alive in the process, but each session contains an unelevated default token that anything running in it is allowed to drop to?

    [You can try to snag that token from WTS (no idea if it'll work), but it won't be the same as launching it in the usual manner since other non-token context is missing (e.g., environment variables inherited from Explorer). -Raymond]
  25. Joshua says:

    As it happened, the guy who said installer bootstraper that runs unprivileged won. Every other solution resulted in at some subset of cases unhandled.

  26. Myria's solution sounds like the one I published back in 2009 and that (AFAIK) predated the MSDN sample:

    blogs.msdn.com/…/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app.aspx

    BTW, there's no need for SeTcbPrivilege – no user account should ever be granted that.  SeDebugPrivilege is OK to assume because you're running as admin, after all.  If you're not admin, then the sample isn't needed.  My blog post also covers the caveats that the MSDN sample skipped mentioning.

  27. Anonymous Coward says:

    I think Joshua is right. If we've learned anything from this article, it is that elevation is very much a one-way street.

Comments are closed.

Skip to main content