Suppose your Windows user account is in the Admin group, UAC is enabled, and you're running some program A with normal user privileges. A never asks for elevation and never receives it. Now suppose A wants to launch program B, which has highestAvailable in its manifest.
If A calls CreateProcess(B), this will fail with error 740 ("elevation required")
If A calls ShellExecuteEx(B), Windows will display a UAC box asking to run B elevated. The user can say Yes, in which case B will run elevated, or No, in which case the launch will fail.
My question is: is there any way to achieve a third option, where we simply launch B without elevation?
It seems like this should be possible in principle, since "highestAvailable" means that B prefers to run with elevation but is perfectly capable of running in normal user mode. But I can't figure out any way to accomplish it. I've tried all sorts of things with tokens and CreateProcessAsUser(), but it all seems to come down to this: "highestAvailable" seems to unalterably refer to the latent privileges inherent in the user account, not the actual privileges expressed in any explicitly constructed token.
I'm hoping that there actually is some way to use CreateProcessAsUser() to do this, and that I'm just missing the trick for properly constructing the token.
Update - solved: the __COMPAT_LAYER=RunAsInvoker solution below works nicely. One caveat, though. This coerces the subprocess to run "as invoker" unconditionally: it applies even if the exe being invoked specifies "requireAdministrator" in its manifest. I think the original "elevation required" error is generally preferable when the exe specifies "requireAdministrator". The whole reason I wanted the RunAsInvoker behavior for programs marked with "highestAvailable" is that such programs are expressly saying "I can function properly in either mode" - so let's go ahead and run in normal user mode when it's inconvenient to use Admin mode. But "requireAdministrator" is a different matter: such programs are saying "I can't function properly without elevated privileges". It seems better to fail up front for such programs than to force them to run un-elevated, which might make them encounter privilege/access errors that they're not properly programmed to handle. So I think a complete, general-purpose solution here would require checking the application manifest, and only applying the RunAsInvoker coercion if the manifest says "highestAvailable". An even completer solution would be to use one of the techniques discussed elsewhere to give the caller an option to invoke UAC when presented with a "requireAdministrator" program and offer the user a chance to launch it elevated. I can imagine a CreateProcessEx() cover with a couple of new flags for "treat process privileges as highest available privileges" and "invoke UAC if elevation is required". (The other approach described below, hooking NTDLL!RtlQueryElevationFlags() to tell CreateProcess() that UAC is unavailable, has exactly this same caveat with respect to requireAdministrator programs.)
(It's probably telling that the Windows shell doesn't even offer a way to do this... launching B directly from the shell would give you the UAC box that lets you either launch with Admin privs or not launch at all. If there were any way to accomplish it, the UAC box might offer a third button to launch without privileges. But then again that could just be a UX decision that the third option is too confusing for civilians.)
(Note that there are quite a lot of posts on StackOverflow and the Microsoft dev support sites asking about a very similar-seeming scenario that unfortunately doesn't apply here. That scenario is where you have a parent program that's running elevated, and it wants to launch a non-elevated child process. The canonical example is an installer, running elevated as installers tend to do, that wants to launch the program it just installed, at normal user level, just before it exits. There's lots of posted code about how to do that, and I've based my attempts on some of those techniques, but this is really a different scenario and the solutions don't work in my situation. The big difference is that the child program they're attempting to launch in this case isn't marked with highestAvailable - the child is just a normal program that would launch without any UAC involvement under normal circumstances. There's another difference as well, which is that in those scenarios, the parent is already running elevated, whereas in my scenario the parent is running as normal user level; that changes things slightly because the parent process in this other scenario has access to a few privileged operations on tokens that I can't use because A isn't itself elevated. But as far as I can tell those privileged token operations wouldn't help anyway; it's the fact that the child has the highestAvailable flag that's the key element of my scenario.)
Set the __COMPAT_LAYER
environment variable to RunAsInvoker
in your process. I don't think this is formally documented anywhere but it works all the way back to Vista.
You can also make it permanent by setting the it under the AppCompatFlags\Layers
key in the registry.