Today’s post is about something I had recently implemented for my customer’s USD project. Over the years I’ve delivered multiple projects with Microsoft CCF, CCA and now USD.
Regardless of the version the framework lacks of functionality to notify the AgentDesktop that something has gone wrong for any of the external hosted apps. There a few cheap and nasty ways to check for the health on an external hosted application such as:
- Poll the process (Hosted App) in particular that needs to be monitored every X number of seconds and write code to relaunch the external app if this one has crashed.
- Add a Windows’ hook and filter events to map them to Windows’ creation or destruction events
Unfortunately, .NET “doesn’t provide” a system-wide way to do this, or does it? I know this could be achieve with a driver that’s running in kernel-mode but how can we write something as sophisticated for something as trivial as listening for process events (creation and destruction). In the past, I had done something with Visual C++ to achieve this as described here and shown in code snippet below
#include "eventsink.h" int main(int iArgCnt, char ** argv) { HRESULT hres; // Step 1: -------------------------------------------------- // Initialize COM. ------------------------------------------ hres = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hres)) { cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl; return 1; // Program has failed. } // Step 2: -------------------------------------------------- // Set general COM security levels -------------------------- hres = CoInitializeSecurity( NULL, -1, // COM negotiates service NULL, // Authentication services NULL, // Reserved RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation NULL, // Authentication info EOAC_NONE, // Additional capabilities NULL // Reserved ); if (FAILED(hres)) { cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl; CoUninitialize(); return 1; // Program has failed. } // Step 3: --------------------------------------------------- // Obtain the initial locator to WMI ------------------------- IWbemLocator *pLoc = NULL; hres = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc); if (FAILED(hres)) { cout << "Failed to create IWbemLocator object. " << "Err code = 0x" << hex << hres << endl; CoUninitialize(); return 1; // Program has failed. } // Step 4: --------------------------------------------------- // Connect to WMI through the IWbemLocator::ConnectServer method IWbemServices *pSvc = NULL; // Connect to the local root\cimv2 namespace // and obtain pointer pSvc to make IWbemServices calls. hres = pLoc->ConnectServer( _bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc ); if (FAILED(hres)) { cout << "Could not connect. Error code = 0x" << hex << hres << endl; pLoc->Release(); CoUninitialize(); return 1; // Program has failed. } cout << "Connected to ROOT\\CIMV2 WMI namespace" << endl; // Step 5: -------------------------------------------------- // Set security levels on the proxy ------------------------- hres = CoSetProxyBlanket( pSvc, // Indicates the proxy to set RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx NULL, // client identity EOAC_NONE // proxy capabilities ); if (FAILED(hres)) { cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl; pSvc->Release(); pLoc->Release(); CoUninitialize(); return 1; // Program has failed. } // Step 6: ------------------------------------------------- // Receive event notifications ----------------------------- // Use an unsecured apartment for security IUnsecuredApartment* pUnsecApp = NULL; hres = CoCreateInstance(CLSID_UnsecuredApartment, NULL, CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, (void**)&pUnsecApp); EventSink* pSink = new EventSink; pSink->AddRef(); IUnknown* pStubUnk = NULL; pUnsecApp->CreateObjectStub(pSink, &pStubUnk); IWbemObjectSink* pStubSink = NULL; pStubUnk->QueryInterface(IID_IWbemObjectSink, (void **) &pStubSink); // The ExecNotificationQueryAsync method will call // The EventQuery::Indicate method when an event occurs hres = pSvc->ExecNotificationQueryAsync( _bstr_t("WQL"), _bstr_t("SELECT * " "FROM __InstanceCreationEvent WITHIN 1 " "WHERE TargetInstance ISA 'Win32_Process'"), WBEM_FLAG_SEND_STATUS, NULL, pStubSink); // Check for errors. if (FAILED(hres)) { printf("ExecNotificationQueryAsync failed " "with = 0x%X\n", hres); pSvc->Release(); pLoc->Release(); pUnsecApp->Release(); pStubUnk->Release(); pSink->Release(); pStubSink->Release(); CoUninitialize(); return 1; } // Wait for the event Sleep(10000); hres = pSvc->CancelAsyncCall(pStubSink); // Cleanup // ======== pSvc->Release(); pLoc->Release(); pUnsecApp->Release(); pStubUnk->Release(); pSink->Release(); pStubSink->Release(); CoUninitialize(); return 0; // Program successfully completed. }
but having to distribute a native DLL that depends on Visual C++ redistributable along with USD customizations is not something very nice or elegant to do, hence I had to come up with a different approach that accomplished the same thing… Enter the ApplicationManager.
The ApplicationManager is a class that contains an instance of WmiListener class. The WmiListener class implements WqlEventQuery, ManagementEventWatcher and ManagementBaseObject. The WqlEventQuery takes a WQL query that applies to the creation and destruction of processes (yes, without having to implement any driver). There are two main classes that provide information for this being __InstanceCreationEvent and __InstanceDeletionEvent The code snippet for WmiListener and its use in ApplicationManager are depicted below
WmiListener
// Delegate use to raise event when a process is created or destroyed public delegate void WmiEventHandler(EventType eventType, object sender, EventArrivedEventArgs e); // Event to notify USD of process creation and destruction public event WmiEventHandler OnWmiEventArrival; /// <summary> /// The instance creation event /// </summary> private const string InstanceCreationEvent = "__InstanceCreationEvent"; /// <summary> /// The instance deletion event /// </summary> private const string InstanceDeletionEvent = "__InstanceDeletionEvent"; /// <summary> /// The WQL win32 process query /// </summary> private const string WqlWin32ProcessQuery = "TargetInstance ISA 'Win32_Process'"; /// <summary> /// Starts the listening. /// </summary> public void StartListening() { try { _mutex.WaitOne(); if (!_isListening) { _processCreationEventWatcher.EventArrived += _processCreationEventWatcher_EventArrived; _processDestructionEventWatcher.EventArrived += _processDestructionEventWatcher_EventArrived; _processCreationEventWatcher.Start(); _processDestructionEventWatcher.Start(); _processCreationObject = _processCreationEventWatcher.WaitForNextEvent(); _processDestructionObject = _processDestructionEventWatcher.WaitForNextEvent(); _isListening = true; } } finally { _mutex.ReleaseMutex(); } } /// <summary> /// Stops the listening. /// </summary> public void StopListening() { try { _mutex.WaitOne(); if (_isListening) { _processCreationEventWatcher.EventArrived -= _processCreationEventWatcher_EventArrived; _processDestructionEventWatcher.EventArrived -= _processDestructionEventWatcher_EventArrived; _processCreationObject?.Dispose(); _processDestructionObject?.Dispose(); _processCreationEventWatcher.Stop(); _processDestructionEventWatcher.Stop(); _isListening = false; } } finally { _mutex.ReleaseMutex(); } }