Is there an easy way to create a normal administrator process (not elevated) from an elevated administrator process? I am using Windows 10 pro. The situation is that I are trying to make some kind of deploy tools. The tool will be running with elevated administrator context in order to write files into "Program Files" ( and access other privileged resources). But one of the step is to invoke an external program. That program seems to have strange issues when created with elevated administrator privilege. We have to launch it in a non-elevated administrator context. I tried the approach in an MSDN blog, https://blogs.msdn.microsoft.com/winsdk/2010/05/31/dealing-with-administrator-and-standard-users-context it does not work at all.
-
Please share some codes and steps on how you have tried and how you are failing. – Muhammad Sumon Molla Selim Jun 21 '16 at 15:15
-
I would if I could find them. But actually I copied the code from the link I provided. I understand the example is trying to show that there are two linked token regarding the elevated administrator and non-elevated administrator. By getting the elevated one, you could find the linked part. Using the linked part, you can get the argument required by CreateProcessWithToken. On Windows 10, the conclusion is not right. Linked token could be fetched, but that one seems to be kind of narrowed version or a view of the token. It just could not be used in CreateProcessWithToken. – H. Tao Jun 21 '16 at 17:57
-
There is a open-source project called ProcessHacker, which per my understanding is a clone of ProcessExplorer from sysinternals. I've ported the code from ProcessHacker. Their approach is getting the elevated Administrator token and adjust its security descriptors. It seems to work for simple programs like NotePad, but failed to launch complex programs as in our case. I tend to believe their manufactured token is an approximation but still not the real one associated with non-elevated administrator. – H. Tao Jun 21 '16 at 18:03
4 Answers
Raymond Chen addressed this exact question on his "Old New Thing" blog on MSDN:
How can I launch an unelevated process from my elevated process and vice versa?
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.
...
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.)
Raymond uses IShellFolderViewDual
and IShellDispatch2
to accomplish that 1:
#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);
}
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));
}
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;
}
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
1: the implementation of FindDesktopFolderView()
is in another article on Raymond's blog:
Manipulating the positions of desktop icons:
void FindDesktopFolderView(REFIID riid, void **ppv)
{
CComPtr<IShellWindows> spShellWindows;
spShellWindows.CoCreateInstance(CLSID_ShellWindows);
CComVariant vtLoc(CSIDL_DESKTOP);
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty,
SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
CComPtr<IShellBrowser> spBrowser;
CComQIPtr<IServiceProvider>(spdisp)->
QueryService(SID_STopLevelBrowser,
IID_PPV_ARGS(&spBrowser));
CComPtr<IShellView> spView;
spBrowser->QueryActiveShellView(&spView);
spView->QueryInterface(riid, ppv);
}

- 555,201
- 31
- 458
- 770
-
Thanks! I did try shellexecute the approach myself and it does not work for me. But my code of shellexecute is rather simple, compared with those you posted. Perhaps the difference does be a key here. I will have a try and report back. – H. Tao Jun 21 '16 at 17:49
-
@H.Tao: you were trying to use the actual `ShellExecute()` function, whereas Raymond is using the `IShellDispatch2.ShellExecute()` interface method to delegate to the un-elevated user's running copy of Explorer instead. – Remy Lebeau Jun 21 '16 at 18:23
-
I did a quick test. So far it works. Thanks! BTW: The solution from Juan. S also works. But I feel the way presented here is more elegant. – H. Tao Jun 22 '16 at 00:18
-
As I just found a use for this code, I've added some error handling via C++ exceptions in [my answer](http://stackoverflow.com/a/43768571/7571258). Thanks! – zett42 May 03 '17 at 19:43
-
There is no reason to use com. I think this way over complicates it. you can simply use ShellExecuteW(nullptr, L"open", L"explorer.exe", "somepathtoopen", nullptr, NULL); – Paul Baxter Nov 29 '18 at 17:51
-
@PaulBaxter AFAIK, that has the potential to run Explorer as the same user and elevation level as the calling process, which defeats the purpose of the question (to run an unelevated process from an elevated process). – Remy Lebeau Nov 29 '18 at 17:53
-
I use this exact code for our html5 help so I don't launch an elevated browser from our elevated tool for security reasons and this works and has been through QA testing – Paul Baxter Nov 29 '18 at 17:57
-
@PaulBaxter if it were that simple, don't you think Raymond Chen would have used it instead of COM? – Remy Lebeau Nov 29 '18 at 17:59
-
@RemyLebeau run the code in an elevated command prompt and have it launch notepad. Open up the Task manager and show UAC column and you will see it is not elevated. A hint about using urls though I launch a launcher - non elevated that launches the users default browser with the url – Paul Baxter Nov 29 '18 at 18:05
-
This one does use `CreateProcessWithToken` instead of `IShellDispatch2::ShellExecute`: https://stackoverflow.com/questions/45915599/how-can-i-unelevate-a-process/45921237#45921237 This means (1) no need to run `explorer.exe` at all to replicate the token, in mine case this was the `smss.exe` process. (2) You still can wait on child close (and many other features related to the `CreateProcess` function), when the `ShellExecute` does not return any child process handle to wait on. – Andry Mar 20 '22 at 16:01
Here is my version of Raymond Chen's code that adds error handling via C++ exceptions.
First we declare some helper functions for throwing std::system_error
exception from HRESULT
, converting a GUID to string and a RAII wrapper for COM initialization.
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
#include <iostream>
#include <system_error>
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
template< typename ResultT = std::string >
ResultT to_string( REFIID riid )
{
LPOLESTR pstr = nullptr;
if( SUCCEEDED( ::StringFromCLSID( riid, &pstr ) ) )
{
// Use iterator arguments to cast from wchar_t to char if element type of ResultT is char.
ResultT result{ pstr, pstr + wcslen( pstr ) };
::CoTaskMemFree( pstr ); pstr = nullptr;
return result;
}
return {};
}
struct ComInit
{
ComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "Could not initialize COM" ); }
~ComInit() { ::CoUninitialize(); }
ComInit( ComInit const& ) = delete;
ComInit& operator=( ComInit const& ) = delete;
};
This is followed by the functions that do the actual work. This is basically the same code as in Remy Lebeau's answer, but with added error handling (and my personal formatting style).
void FindDesktopFolderView( REFIID riid, void **ppv )
{
CComPtr<IShellWindows> spShellWindows;
ThrowIfFailed(
spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
"Could not create instance of IShellWindows" );
CComVariant vtLoc{ CSIDL_DESKTOP };
CComVariant vtEmpty;
long lhwnd = 0;
CComPtr<IDispatch> spdisp;
ThrowIfFailed(
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
"Could not find desktop shell window" );
CComQIPtr<IServiceProvider> spProv{ spdisp };
if( ! spProv )
ThrowIfFailed( E_NOINTERFACE, "Could not query interface IServiceProvider" );
CComPtr<IShellBrowser> spBrowser;
ThrowIfFailed(
spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser) ),
"Could not query service IShellBrowser" );
CComPtr<IShellView> spView;
ThrowIfFailed(
spBrowser->QueryActiveShellView( &spView ),
"Could not query active IShellView" );
ThrowIfFailed(
spView->QueryInterface( riid, ppv ),
"Could not query interface " + to_string( riid ) + " from IShellView" );
}
void GetDesktopAutomationObject( REFIID riid, void **ppv )
{
CComPtr<IShellView> spsv;
FindDesktopFolderView( IID_PPV_ARGS(&spsv) );
CComPtr<IDispatch> spdispView;
ThrowIfFailed(
spsv->GetItemObject( SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView) ),
"Could not get item object SVGIO_BACKGROUND from IShellView" );
ThrowIfFailed(
spdispView->QueryInterface( riid, ppv ),
"Could not query interface " + to_string( riid ) + " from ShellFolderView" );
}
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;
ThrowIfFailed(
spFolderView->get_Application( &spdispShell ),
"Could not get application object from IShellFolderViewDual" );
CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
if( !spdispShell2 )
ThrowIfFailed( E_NOINTERFACE, "Could not query interface IShellDispatch2" );
ThrowIfFailed(
spdispShell2->ShellExecute(
CComBSTR{ pszFile },
CComVariant{ pszParameters ? pszParameters : L"" },
CComVariant{ pszDirectory ? pszDirectory : L"" },
CComVariant{ pszOperation ? pszOperation : L"" },
CComVariant{ nShowCmd } ),
"ShellExecute failed" );
}
Usage example:
int main()
{
try
{
ComInit init;
ShellExecuteFromExplorer( L"http://www.stackoverflow.com" );
}
catch( std::system_error& e )
{
std::cout << "ERROR: " << e.what() << "\n"
<< "Error code: " << e.code() << std::endl;
}
}
Additional notes:
When using this method, you may notice that the window of the launched application does not always come to the foreground, especially if it is already running. My workaround is to call AllowSetForegroundWindow( ASFW_ANY )
before the call to ShellExecuteFromExplorer()
to enable the process to bring itself to the foreground (we can't specify a process ID because we don't know the process that will be launched beforehand).

- 25,437
- 3
- 35
- 72
Here's the Raymond Chen method, with error handling, and no wretched exceptions.
#include <atlbase.h>
#include <Shlobj.h>
HRESULT FindDesktopFolderView(REFIID riid, void **ppv)
{
HRESULT hr;
CComPtr<IShellWindows> spShellWindows;
hr = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
if(FAILED(hr))
return hr;
CComVariant vtLoc { 0 }; // 0 = CSIDL_DESKTOP
CComVariant vtEmpty;
long lhwnd = 0;
CComPtr<IDispatch> spdisp;
hr = spShellWindows->FindWindowSW(&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
if(FAILED(hr))
return hr;
CComQIPtr<IServiceProvider> spProv{ spdisp };
if(!spProv)
return E_NOINTERFACE;
CComPtr<IShellBrowser> spBrowser;
hr = spProv->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser));
if(FAILED(hr))
return hr;
CComPtr<IShellView> spView;
hr = spBrowser->QueryActiveShellView(&spView);
if(FAILED(hr))
return hr;
return spView->QueryInterface(riid, ppv);
}
HRESULT GetDesktopAutomationObject(REFIID riid, void **ppv)
{
HRESULT hr;
CComPtr<IShellView> spsv;
hr = FindDesktopFolderView(IID_PPV_ARGS(&spsv));
if(FAILED(hr))
return hr;
if(!spsv)
return E_NOINTERFACE;
CComPtr<IDispatch> spdispView;
hr = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
if(FAILED(hr))
return hr;
return spdispView->QueryInterface(riid, ppv);
}
HRESULT ShellExecuteFromExplorer(PCWSTR pszFile, PCWSTR pszParameters, PCWSTR pszDirectory, PCWSTR pszOperation, int nShowCmd)
{
HRESULT hr;
CComPtr<IShellFolderViewDual> spFolderView;
hr = GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
if(FAILED(hr))
return hr;
CComPtr<IDispatch> spdispShell;
hr = spFolderView->get_Application(&spdispShell);
if(FAILED(hr))
return hr;
CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
if(!spdispShell2)
return E_NOINTERFACE;
// without this, the launched app is not moved to the foreground
AllowSetForegroundWindow(ASFW_ANY);
return spdispShell2->ShellExecute(
CComBSTR{ pszFile },
CComVariant{ pszParameters ? pszParameters : L"" },
CComVariant{ pszDirectory ? pszDirectory : L"" },
CComVariant{ pszOperation ? pszOperation : L"" },
CComVariant{ nShowCmd } );
}

- 439
- 4
- 14
The solution is a bit complex. Maybe you can not go from elevated administrator to non-elevated administrator directly by getting a kind of access token and passing it to createprocesswithtoken, But you can go one step further in another direction. You can go from elevated administrator to system account, which has even higher privilege. From system account privilege, you should be able to launch a process in a non-elevated administrator context. Search using keyword "impersonate" can give you a lot of examples. Then how to go from elevated administrator to system? You can only write a system service and create/start the service under elevated administrator context.

- 36
- 5
-
The reason why I choose this complex answer as the one "working for me" is: I can get a process ID returned for following wait functions. In the shellexecute approach, I did not find an easy way to identify the process ID which was exactly started by my codes. – H. Tao Jun 23 '16 at 00:11
-
Can you give more details about this impersonation? Does one need the user's password to do this? – zimmerrol Aug 15 '17 at 08:20