Enable real-time notifications of external hosted apps in USD (Unified Service Desk) with WMI

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();
			}
		}

Leave a Reply

Your email address will not be published. Required fields are marked *