The ShellÂExecuteÂEx
function and IContextÂMenu
interface provide the caller a number of places to customize how the execution occurs by allowing the call to pass a “site” which ShellÂExecuteÂEx
/IContextÂMenu
will access at various points in the execution process.
Today we’ll demonstrate this technique by adding environment variables to processes launched via ShellÂExecuteÂEx
and IContextÂMenu
.
The ICreatingÂProcess
interface is used by ShellÂExecuteÂEx
. and IContextÂMenu
to allow the caller to customize how processes are created. The extension point is obtained by querying the site for SID_
and requesting an ICreatingÂProcess
. If one is produced, then the system calls the OnCreating
method with an object that allows the creation to be customized.
Today’s C++ COM library is (rolls dice) C++/WinRT.
struct AddEnvironmentVariableSite : winrt::implements<AddEnvironmentVariableSite, ::IServiceProvider, ::ICreatingProcess> { IFACEMETHOD(QueryService) (REFGUID service, REFIID riid, void** ppv) { if (service == SID_ExecuteCreatingProcess) { return this->QueryInterface(riid, ppv); } else { *ppv = nullptr; return E_NOTIMPL; } } IFACEMETHOD(OnCreating)(ICreateProcessInputs* inputs) { return inputs->SetEnvironmentVariable( L"EXTRAVARIABLE", L"Bonus"); } };
This site responds to SID_
by returning itself, and it implements ICreatingÂProcess
by having its OnCreating
method set an environment variable called EXTRAVARIABLE
with a value of Bonus
. Since that is the only thing we do, we can just return the result of SetÂEnvironmentÂVariable()
as our own return value. If you intend to add multiple environment variables, you would check the return value of each call to SetÂEnvironmentÂVariable()
.
In general, your custom site would be part of a so-called “site chain” and forward any unhandled services to your own site, so that an outer site could respond to it.
Here’s an example of how to use this special environment variable site in conjunction with ShellÂExecuteÂEx
:
BOOL Sample() { SHELLEXECUTEINFO sei{ sizeof(sei) }; sei.lpFile = LR"(C:\Windows\system32\charmap.exe)"; sei.nShow = SW_SHOWNORMAL; auto site = winrt::make_self<AddEnvironmentVariableSite>(); sei.hInstApp = reinterpret_cast<HINSTANCE>(site.get()); sei.fMask = SEE_MASK_FLAG_HINST_IS_SITE; return ShellExecuteEx(&sei); }
To pass a site to ShellÂExecuteÂEx
, we put it in the hInstApp
member and set the SEE_
flag to say “If you look at the hInstApp
, you’ll find a site!”¹
For context menus, we explicitly set our custom site as the context menu’s site. Building on our original sample for hosting an IContextÂMenu
:
void OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos) { IContextMenu *pcm; if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi", IID_IContextMenu, (void**)&pcm))) { HMENU hmenu = CreatePopupMenu(); if (hmenu) { if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL))) { CMINVOKECOMMANDINFO info = { 0 }; info.cbSize = sizeof(info); info.hwnd = hwnd; info.lpVerb = "play"; auto site = winrt::make_self<AddEnvironmentVariableSite>(); IUnknown_SetSite(pcm, site.get()); pcm->InvokeCommand(&info); IUnknown_SetSite(pcm, nullptr); } DestroyMenu(hmenu); } pcm->Release(); } }
(I’m ignoring the fact that winrt::
can throw an exception which results in a memory leak in the sample code above.)
¹ This is admitted a rather ugly way to pass a site, but the ability to add a site was retrofitted onto the existing structure, so we had to hide it in an other-wise unused input member.
Why does IServiceProvider exist when we have IUnknown::QueryInterface?
To answer a question with a question, why does the object that implements IServiceProvider also have to implement ICreatingProcess? This did happen for the above sample, but this isn’t a requirement. IServiceProvider was designed for cases where an object didn’t implement all of the interfaces.
IServiceProvider allows an object to say “I don’t implement that interface, but I know someone who does!” This is more general than QueryInterface, which requires the object to implement the interface directly. In our example, the service provider also implements ICreatingProcess, but in the general case, it could have delegated it to another object.
Oh, interestingly, the docs for IServiceProvider list the minimum client and server versions as Windows 11 (22000).
This is the first I hear of ICreatingProcess, and I can’t seem to find any matches for it when searching online.
https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-icreatingprocess
First Google result for “msdn icreatingprocess”, can also be found by searching on Microsoft Learn directly.
MSDN (futuristically known as Microsoft Learn) says that the flag needs to be manually defined prior to Windows 8, but ICreatingProcess is documented to have minimum OS Vista. Does that mean the flag works in Vista and 7, just undocumented, or that SEE will fail in Vista and 7 with that flag, or that SEE succeeds without considering the flag in Vista and 7?
Also, IContextMenu and SEE don’t always create processes (e.g., IDropTarget and a...
It’s a doc bug. SEE_MASK_FLAG_HINST_IS_SITE is not supported prior to Windows 8. I will submit a doc fix request.
The most likely thing that happened was that it wasn’t put into the public headers, but Windows supported it.
It is also possible that this was functionality added into Windows via a patch, and this meant that it wasn’t defined in any of the public headers until the Windows 8 SDK. But in that kind of situation, you would expect the KB number to be listed which introduced the functionality.
Even for Microsoft this seems like a bad hack. Reusing a field that doesn't actually represent what it says it is just smells bad. This is especially bothersome since changing the environment variables of a new process is not that uncommon.
The SHELLEXECUTEINFO type has the size as the first parameter. As has been done in several other places in Win32 over the years, adding new fields to the struct increases the struct size. Hence a...