Back to fire after some good & sunny holidays and a cool team offsite in Lisbon with my european colleagues Tess, Doug, Carlo, Johan and all others.
My today's take will be related to IIS6 and the logical execution context of its worker process, especially its related windowstation and desktops
First as quick terminology remind quoted from About Window Stations and Desktops:
"A window station is a securable object that is associated with a process, and contains a clipboard, an atom table, and one or more desktop objects
A desktop is a securable object contained within a window station. A desktop has a logical display surface and contains user interface objects such as windows, menus, and hooks."
When talking about those objects, we have to keep in mind we are dealing with secured objects that have dedicated ACLs.
Each process is running within a window station that might contain one or several desktops
When talking about the “identity stamp” of a process, we are talking about a security token,
Those tokens contain group membership, privileges, and relate to a logon session that is the “atomic materialization” of a user identity logon.
If you’re interested I can only recommend digging into Keith Brown articles: first MSJ 99 and if you want to try to write your own first Logon Session Broker MSJ 2000
The security context of an IIS6 worker process will be basically built from your application identity settings.
By default, IIS6 worker processes are running Network Service identity, within a single non-interactive windows station (created by the WWW service) and sharing the same desktop.
If you configure a specific identity for your application pools, then each worker process will rely by default its own desktop object.
If you are using several applications or important web gardens, you might experience desktop heap exhaustion, with various symptoms like Q217202 or Q870655
To fix that kind of desktop heap exhaustion issue, you have 2 point of views:
· Try to tune the desktop heap settings
· Configure IIS to share a single desktop object for all worker processes running the same identity by using UseSharedWPDesktop registry key
Since tuning desktop heap is a machine wide setting and can be hard to correctly evaluate, I personally prefer sharing a single desktop.
Paranoiac administrators might see a security issue by sharing anything between processes, but as David Wang explains (quoted):
“The following are the interesting combinations and consequences:
1. If UseSharedWPDesktop=0 and all application pools launch as Network Service, then there is exactly one Desktop (of Network Service) shared by all the application pools.
You have no isolation of desktop nor process identity between application pools.
2. If UseSharedWPDesktop=0 and all application pools launch as unique user identity, then there is a unique Desktop for each user identity, which is obviously not shared.
This is limited to a number around 60 due to Desktop heap limit in Windows Server 2003. This means that you can only have ~60 application pools perfectly isolated by desktop and process identity simultaneously running.
3. If UseSharedWPDesktop=1 and all application pools launch as unique user identity, then there is one Desktop that is shared by all the user identities.
This removes the limit of 60 at the cost of not perfectly isolated application pools. You have no desktop isolation but retain process identity isolation between application pools.
4. UseSharedWPDesktop=1 and all application pools launch as Network Service is pretty much a degenerate case that is not interesting. :-)”
Vista and IIS7 shouldn’t really have those desktop heap exhaustion issues, because of Vista’s Dynamic Memory Architecture (but that's here also a very long story).
You should anyway continue taking care of your application pool identities, since it will have many impacts on the way your code will be housed by Windows.
Another possible kind of issue is because of some applications to fail on IIS6 is that they need an interactive execution.
IIS creates its window stations as non-interactive, and to be honest I didn’t find any supported way to change it (but maybe there is...).
That means you could potentially experience issues with components like GDI/GDI+ (get a look at Carlo’s blog), or using some printer drivers (using WMI or prnadmin.dll), etc…
I even have to admit I saw couple of times some ISAPI filters having debugging spies shown using a MessageBox...
I think we sometimes have to remember that IIS is a web server, designed to host scalable application servers, that is IMO *not* something’s really compatible with interactive actions.
IIS is designed to process in a timely optimized fashion *silent* server application, and using graphical & printer related things is normally not (again at least from my experience).
Then the right design to allow that kind of interactive features is to have a separate process running within interactive window station.
It can for sure communicate with IIS process by any supported IPC manner (COM interfaces, namedpipes, MMF, sockets, etc..)
Also an easy design to do things like printer management might just be using interactive scheduled task in order to avoid any driver related issue...
BHO means “Browser Helper Object”, that’s an IE plugin that interacts with browser & user events.
Basically this is a COM component that implements IObjectWithSite and is registered under
“HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects”
A typical BHO could be a pop-up blocker, customizing at client-side an HTML document.
Some of them are also doing some UI stuffs, like adding menu features or acting as a toolbar
Note however those toolbars & co. don’t always require to be a registered as BHOs.
For a step by step BHO go to Building Browser Helper Objects with Visual Studio 2005.
For a sample step by step toolbar, go to that tutorial .
That being said, we might get a great user experience from IE by reacting to its application...
That could make inside IE a kind of “rich intranet application”, interactions between client side scripts, and extensions.
We can imagine different kind of interaction with that schema (non exhaustive list…):
· Calling a method exposed by your browser extension from JavaScript
· Calling JavaScript method/event from your browser extension
· Modify the HTML / JS code from your browser extension
This post will for now only speak about calling a BHO method from your scripting code, maybe in future posts I’ll describe other possible things with some samples.
To get an IE extensions that changes some user UI (menu items, mouse gesture, etc...), we can easily implement in our BHO IDocHostUIHandler and attach it to current document using ICustomDoc::SetUIHandler()
That trick also allows to “extend” the HTML external object by implementing some custom window.external.foo() methods.
When making that window.external call, the webbrowser control will call into IDocHostUIHandler::GetExternal() to get the UI handler IDispatch interface to invoke
This means you just need to implement it to send back your own interface to make it callable.
The problem I wanted to discuss in that post is that there is no existing design for chaining UIHandlers… Indeed UIHandlers exist to allow extending user interface within a custom application that hosts a WebBrowser control, but not for Internet Explorer itself that already exposes a UI...
IOW from a BHO you might not be able to add your own menu items, but only to replace the all menu.
That’s basically the same for HTML external object, IE also relies on that external object to implement some UI features through IShellUIHelper & IShellUIHelper2 interfaces (like Favorites & find dialogs, or runonce and search providers with IE7).
That kind of problem you might have is described by Q330441 (PRB: ICustomDoc::SetUIHandler Causes Changes in Save As Dialog)
If you replace the UI Handler within IE by your BHO instance (in order to answer window.external() calls), you will break thereby several UI related features, that is a highly non desirable behavior.
In order to workaround that design issue, some people have implemented within their BHO a dispatch gateway towards builtin interfaces IShellUIHelper & IShellUIHelper2.
That was quite easy to do, just chaining within our BHO IDispatch methods GetIDsOfNames() & Invoke() to standard IDispatch shell interfaces and was working pretty well.
Unfortunately for some reasons I would need to discuss longer in a next post, that might not be working anymore as expected on IE7, getting instead an E_FAIL result.
The best way I could recommend to call your BHO from a script is to come back to a supported way...One of the easiest method is to create a simple ActiveX that is called from your script, and fires BHO methods. To do that, we basically only have to get the BHO instance from the ActiveX and to call its methods.
A good method is to rely on IE session to store a variant we’ll know about on each side (using IWebBrowser2::PutProperty() / IWebBrowser2::GetProperty()).
In the code bellow I register my BHO instance in session variable called “MyBHO_IDisp”. That allows my BHO to “register” for later use from ActiveX.
STDMETHODIMP CObjectBHO::SetSite(IUnknown* pUnkSite)
{
…
// Registers the IE session to allow ActiveX to call into it
hr = ReferenceMe(false);
if (FAILED(hr))
return S_FALSE;
…
}
HRESULT CObjectBHO::ReferenceMe(bool bRemove)
{
BSTR bstrThisKey = SysAllocString(L"MyBHO_IDisp");
VARIANT vThis;
HRESULT hr = S_FALSE;
if (!bRemove)
{
if (!m_spWebBrowserApp)
goto Cleanup;
// Save this to a variant that will be referenced in IE Session
VariantInit(&vThis);
vThis.vt = VT_DISPATCH;
vThis.pdispVal = static_cast<IDispatch*>(this);
// Add our this pointer to IE session by adding a named property
if (FAILED( m_spWebBrowserApp->PutProperty(bstrThisKey, vThis) ))
goto Cleanup;
}
else
{
VariantInit(&vThis);
vThis.vt = VT_NULL;
// Time to release, remove our reference
if (FAILED( m_spWebBrowserApp->PutProperty(bstrThisKey, vThis) ))
goto Cleanup;
}
hr = S_OK;
Cleanup:
VariantClear(&vThis);
SysFreeString(bstrThisKey);
return hr;
}
My ActiveX object now only has to implement IObjectWithSiteImpl::SetSite() to save the client site instance, and to grab the BHO instance from its session properties (GrabBHOInstance())
// CMyClass
class ATL_NO_VTABLE CMyClass :
public CComObjectRootEx<CComSingleThreadModel>,
public CComControl<CMyClass>,
public CComCoClass<CMyClass, &CLSID_MyClass>,
public IObjectWithSiteImpl <CMyClass>,
public IDispatchImpl<IMyClass, &IID_IMyClass, &LIBID_MySampleATLLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
private:
IDispatch *m_pBHODisp;
CComPtr<IUnknown> _spUnkSite;
HRESULT(GrabBHOInstance)(void);
public:
CMyClass()
{
}
STDMETHODIMP CMyClass::SetSite(IUnknown* pUnkSite)
{
HRESULT hr = S_FALSE;
// Save our client site instance
if (pUnkSite)
{
_spUnkSite = pUnkSite;
hr = S_OK;
}
// Try to grab BHO
GrabBHOInstance();
return hr;
}
Here is the implementation for GrabBHOInstance, just getting property from IWebBrowser2::GetProperty()
HRESULT CMyClass::GrabBHOInstance()
{
IServiceProvider* pISP = NULL;
IWebBrowser2* pBrowser = NULL;
BSTR bstrBHOKey = SysAllocString(L"MyBHO_IDisp");
VARIANT vBHO;
HRESULT hr = S_FALSE;
VariantInit(&vBHO);
// Get the IWebBrowser2 interface
if (!_spUnkSite)
goto Cleanup;
if (FAILED(_spUnkSite->QueryInterface(IID_IServiceProvider, (void **)&pISP) ))
goto Cleanup;
if (FAILED( pISP->QueryService(IID_IWebBrowserApp,
IID_IWebBrowser2, (void **)&pBrowser)))
goto Cleanup;
// Get the BHO instance pointer
if (FAILED( pBrowser->GetProperty(bstrBHOKey, &vBHO) ))
goto Cleanup;
// Ensure it's valid and reference count it
if (vBHO.vt == VT_DISPATCH && vBHO.pdispVal != NULL)
{
m_pBHODisp = vBHO.pdispVal;
m_pBHODisp->AddRef();
}
hr = S_OK;
Cleanup:
VariantClear(&vBHO);
SysFreeString(bstrBHOKey);
if (pBrowser != NULL)
{
pBrowser->Release();
pBrowser = NULL;
}
if (pISP != NULL)
{
pISP->Release();
pISP = NULL;
}
return hr;
}
STDMETHODIMP CMyClass::get_IsBHOInstalled(VARIANT_BOOL* pVal)
{
// Check if our BHO instance could be get from the SetSite() call
if (m_pBHODisp)
*pVal = true;
else
*pVal = false;
return S_OK;
}
A simple ActiveX method now can call into our BHO
STDMETHODIMP CMyClass::SayHelloFromBHO(BSTR* pVal)
{
CComVariant varResult;
HRESULT hr = S_FALSE;
DISPPARAMS params = { NULL, NULL, 0, 0 };
// Ensure we have the BHO instance propertly got
if (!m_pBHODisp)
if (FAILED(GrabBHOInstance()))
return hr;
// Forward to BHO instance if valid
if (m_pBHODisp)
{
hr = m_pBHODisp->Invoke(DISP_MYBHOMETHOD, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
// Return the BSTR we got.
*pVal = SysAllocStringLen(varResult.bstrVal, SysStringLen(varResult.bstrVal));
}
return hr;
}
Bellow a sample of the Jscript needed to call your ActiveX, that will fire a method from your browser extension.
var myATL = new ActiveXObject("MySampleATL.MyClass");