I would like a process to always run at the user level. Either when it is launched by the installer (custom, not msi), which runs as at the administrator level, or when a user logs on. Looking around, I'm not sure this is possible.
The easiest way is to have 2 processes. One is normal user and it launches elevated/admin process. Then admin process can use IPC to ask normal user process to do things.
If you have no normal user process, then Raymond Chen documents:
Going from an unelevated process to an elevated process is easy. You can run a process with elevation by passing the runas verb to ShellExecute or ShellExecuteEx.
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.)
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> 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); } 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 GetDesktopAutomationObject 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 ShellFolderView, and the C++ interface for that is IShellFolderViewDual, so most callres 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 ShellExecuteFromExplorer 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 ShellFolderView 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 ShellFolderView, we ask for the Application property so that we can get to the main Shell.Application object, which has the IShellDispatch interface (and its extensions IShellDispatch2 through IShellDispatch6) as its C++ interfaces. And it is the IShellDispatch2::ShellExecute method that is what we really want.
And we call IShellDispatch2::ShellExecute with the appropriate parameters. Note that the parameters to IShellDispatch2::ShellExecute are in a different order from the parameters to ShellExecute!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With