Launching a process as a normal user from an elevated user.

A frequent question from our customers is the ability to launch a non-elevated user from an elevated user.  This can typically happen from an installer application which is elevated and you want to launch an application as a non-elevated user.

It turns out you can use CreateProcessWithTokenW() to launch a non-elevated process from an elevated user.  The caller just needs to be an Administrator (elevated).  You do not need to provide any additional privileges or rights.

The only issue is providing a non-elevated token to CreateProcessWithTokenW() (https://msdn.microsoft.com/en-us/library/windows/desktop/ms682434(v=vs.85).aspx) which represents a non-elevated user.  The best process to target is the shell process which is typically explorer.exe. You can change the shell process via the registry so it is possible that another process maybe running as the shell which isn’t explorer.exe.  Here is the registry key:

Key:
HKEY_LOCAL_MACHINE \SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

Name: Shell

Type: REG_SZ

Value: [Explorer.exe
or Cmd.exe or PowerShell.exe or others]

Once you have determined the non-elevated process to target, you can use the ToolHelp APIs (https://msdn.microsoft.com/en-us/library/windows/desktop/ms686837(v=vs.85).aspx) or the PSAPI APIs (https://msdn.microsoft.com/en-us/library/windows/desktop/ms686941(v=vs.85).aspx) to enumerate all the running processes to find the targeted process.  Once you have found the correct process, you can call OpenProcess() and OpenProcessToken() to obtain the token. Once you have duplicated the token via DuplicateTokenEx(), you can pass it to CreateProcessWithTokenW().

Since it is possible that you could have multiple instances of the shell running on the system in different session (Remote Desktop Server), you should verify that you have the shell
process that is running in the same session as your application.  You can use ProcessIdToSessionId() to get the session ID for a process.  You can call WTSQuerySessionInformation() to determine the current session you are running in.  You can then compare the session IDs.

You do NEED to obtain a token of the INTERACTIVE user (user who logged on via CTRL-ALT-DELETE since they have access to the INTERACTIVE desktop) so that you don’t have to deal with desktop security.  The code becomes much more complex if you obtain a token from a different user or if you decide to
generate your own token with LogonUser(). I strongly suggest you NOT go this route.

Finally, you could call WTSQueryUserToken() to obtain the token of the logged on user.  The issue is that the caller needs to have the SE_TCB_PRIVILEGE (no user is granted this privilege by default other than LocalSystem).  I would NOT recommend granting this privilege to any user (even an Administrator).