//=============================================================================
//
//  Copyright (c) 1996-1999, Microsoft Corporation, All rights reserved
//
//  CONPROV.CPP
//
//  This file implements the classes for event consumer provider caching.
//
//  Classes implemented:
//
//      CConsumerProviderRecord  --- a single consumer provider record
//      CConsumerProviderCache  --- a collection of records.
//
//  History:
//
//  11/27/96    a-levn      Compiles.
//
//=============================================================================
#include "precomp.h"
#include "ess.h"
#include "consprov.h"
#include <provinit.h>
#include <genutils.h>
#include <cominit.h>
#include "NCEvents.h"

CConsumerProviderRecord::CConsumerProviderRecord(CEssNamespace* pNamespace)
        : m_pLogicalProvider(NULL), m_pConsumerProvider(NULL),
        m_pSink(NULL), m_bResolved(FALSE), m_pNamespace(pNamespace),
        m_lRef(0), m_wszMachineName(NULL), m_wszProviderName(NULL), 
        m_wszProviderRef(NULL), m_bAnonymous(FALSE)
{
    m_pNamespace->AddRef();
}



HRESULT CConsumerProviderRecord::Initialize(
                                    IWbemClassObject* pLogicalProvider,
                                    LPCWSTR wszProviderRef,
                                    LPCWSTR wszProviderName,
                                    LPCWSTR wszMachineName)
{
    m_LastAccess = CWbemTime::GetCurrentTime();

    m_pLogicalProvider = pLogicalProvider;
    m_pLogicalProvider->AddRef();

    if(wszMachineName)
        m_wszMachineName = CloneWstr(wszMachineName);

    m_wszProviderName = CloneWstr(wszProviderName);
    m_wszProviderRef = CloneWstr(wszProviderRef);

    if(m_wszProviderName == NULL || m_wszProviderRef == NULL)
        return WBEM_E_OUT_OF_MEMORY;

    // Extract the CLSID
    // =================

    VARIANT vClassId;
    VariantInit(&vClassId);
    CClearMe cm(&vClassId);

    HRESULT hres = pLogicalProvider->Get(CONSPROV_CLSID_PROPNAME, 0, &vClassId, 
                        NULL, NULL);

    if(FAILED(hres) || V_VT(&vClassId) != VT_BSTR) 
    {
        ERRORTRACE((LOG_ESS, "Class ID is missing from consumer "
            "provider record!!\n"));
        return hres;
    }

    if(FAILED(CLSIDFromString(V_BSTR(&vClassId), &m_clsid)))
    {
        ERRORTRACE((LOG_ESS, "INVALID Class ID in consumer "
            "provider record!!\n"));
        return WBEM_E_INVALID_PROVIDER_REGISTRATION;
    }

    return WBEM_S_NO_ERROR;
}

CConsumerProviderRecord::~CConsumerProviderRecord()
{
    _DBG_ASSERT( m_pNamespace != NULL );

    if(m_pLogicalProvider)
        m_pLogicalProvider->Release();
    if(m_pSink)
        m_pNamespace->PostponeRelease(m_pSink);
    if(m_pConsumerProvider)
        m_pNamespace->PostponeRelease(m_pConsumerProvider);
    
    if(m_pSink || m_pConsumerProvider)
    {
        //
        // Report the MSFT_WmiConsumerProviderUnloaded event.
        //
        FIRE_NCEVENT(
            g_hNCEvents[MSFT_WmiConsumerProviderUnloaded], 
            WMI_SENDCOMMIT_SET_NOT_REQUIRED,

            // Data follows...
            m_pNamespace->GetName(),
            m_wszProviderName,
            m_wszMachineName);
    }
    
    if(m_pNamespace)
        m_pNamespace->Release();
    delete [] m_wszMachineName;
    delete [] m_wszProviderName;
    delete [] m_wszProviderRef;
}

long CConsumerProviderRecord::AddRef()
{
    return InterlockedIncrement(&m_lRef);
}

long CConsumerProviderRecord::Release()
{
    long lRef = InterlockedDecrement(&m_lRef);
    if(lRef == 0)
        delete this;
    return lRef;
}

void CConsumerProviderRecord::Invalidate()
{
    IWbemUnboundObjectSink* pSink;
    IWbemEventConsumerProvider* pConsumerProvider;

    {
        CInCritSec ics(&m_cs);

        pSink = m_pSink;
        m_pSink = NULL;

        pConsumerProvider = m_pConsumerProvider;
        m_pConsumerProvider = NULL;

        m_bResolved = FALSE;
    }
        
    _DBG_ASSERT( m_pNamespace != NULL );

    if (pSink)
        m_pNamespace->PostponeRelease(pSink);
    
    if (pConsumerProvider)
        m_pNamespace->PostponeRelease(pConsumerProvider);

    if (pConsumerProvider || pSink)
    {
        //
        // Report the MSFT_WmiConsumerProviderUnloaded event.
        //
        FIRE_NCEVENT(
            g_hNCEvents[MSFT_WmiConsumerProviderUnloaded], 
            WMI_SENDCOMMIT_SET_NOT_REQUIRED,

            // Data follows...
            m_pNamespace->GetName(),
            m_wszProviderName,
            m_wszMachineName);
    }    
}

HRESULT CConsumerProviderRecord::ValidateConsumer(
                                    IWbemClassObject* pLogicalConsumer)
{
    HRESULT hres;

    // Check if consumer provider is cached
    // ====================================

    IWbemEventConsumerProvider* pConsumerProvider = NULL;
    IWbemEventConsumerProviderEx* pConsumerProviderEx = NULL;

    BOOL bResolved = FALSE;
    
    {
        CInCritSec ics(&m_cs);
        m_LastAccess = CWbemTime::GetCurrentTime();

        if(m_bResolved)
        {
            if(m_pConsumerProviderEx)
            {
                pConsumerProviderEx = m_pConsumerProviderEx;
                pConsumerProviderEx->AddRef();
            }
            else
            {
                pConsumerProvider = m_pConsumerProvider;
                if(pConsumerProvider)
                    pConsumerProvider->AddRef();
            }

            bResolved = TRUE;
        }
    }

    // Resolve if not cached
    // =====================

    if(!bResolved)
    {
        IWbemUnboundObjectSink* pGlobalSink;

        hres = ResolveAndCache(&pGlobalSink, &pConsumerProvider, 
                                &pConsumerProviderEx);
        if(FAILED(hres)) return hres;

        if(pGlobalSink)
            pGlobalSink->Release();
    }

    CReleaseMe rm1(pConsumerProvider);
    CReleaseMe rm2(pConsumerProviderEx);

    if(pConsumerProvider == NULL && pConsumerProviderEx)
    {
        //
        // Clearly, this consumer does not support validation
        //

        return WBEM_S_FALSE;
    }

    try
    {
        if(pConsumerProviderEx)
        {
            hres = pConsumerProviderEx->ValidateSubscription(pLogicalConsumer);
        }
        else
        {
            //
            // Old-type provider --- we can still achieve validation by calling
            // FindConsumer --- it might reject this consumer at that time
            //

            IWbemUnboundObjectSink* pSink = NULL;
            hres = pConsumerProvider->FindConsumer(pLogicalConsumer, &pSink);
            if(SUCCEEDED(hres) && pSink)
                pSink->Release();
        }
    }
    catch(...)
    {
        ERRORTRACE((LOG_ESS, "Event consumer provider %S in namespace %S "
            "threw an exception in ValidateConsumer/FindConsumer\n", 
                m_wszProviderName, m_pNamespace->GetName()));
        hres = WBEM_E_PROVIDER_FAILURE;
    }

    return hres;
}
    

HRESULT CConsumerProviderRecord::GetGlobalObjectSink(
                OUT IWbemUnboundObjectSink** ppSink, 
                IN IWbemClassObject *pLogicalProvider)
{
    *ppSink = NULL;

    // Check of a cached version is available
    // ======================================

    {
        CInCritSec ics(&m_cs);
        m_LastAccess = CWbemTime::GetCurrentTime();

        if(m_bResolved)
        {
            // It is --- return it
            // ===================

            *ppSink = m_pSink;
            if(m_pSink)
                m_pSink->AddRef();
            return WBEM_S_NO_ERROR;
        }
    }

    // No cached version --- retrieve it
    // =================================

    IWbemUnboundObjectSink* pSink;
    IWbemEventConsumerProvider* pConsumerProvider;
    IWbemEventConsumerProviderEx* pConsumerProviderEx;

    HRESULT hres = ResolveAndCache(&pSink, &pConsumerProvider, 
                                    &pConsumerProviderEx);
    if(FAILED(hres))
        return hres;

    if(pConsumerProvider)
        pConsumerProvider->Release();
    if(pConsumerProviderEx)
        pConsumerProviderEx->Release();
    
    *ppSink = pSink;

    if (*ppSink != NULL)
    {
        //
        // Report the MSFT_WmiConsumerProviderSinkLoaded event.
        //
        FireNCSinkEvent(
            MSFT_WmiConsumerProviderSinkLoaded,
            pLogicalProvider);
    }

    return WBEM_S_NO_ERROR;
}

HRESULT CConsumerProviderRecord::ResolveAndCache(
                            IWbemUnboundObjectSink** ppSink,
                            IWbemEventConsumerProvider** ppConsumerProvider,
                            IWbemEventConsumerProviderEx** ppConsumerProviderEx)
{
    // Resolve it first
    // ================

    HRESULT hres = Resolve(ppSink, ppConsumerProvider, ppConsumerProviderEx);
    if(FAILED(hres))
        return hres;

    // Cache if needed
    // ===============

    {
        CInCritSec ics(&m_cs);
        m_LastAccess = CWbemTime::GetCurrentTime();
        
        if(m_bResolved)
        {
            // Already cached.  Release ours. 
            // ==============================

            if(*ppSink)
                (*ppSink)->Release();
            if(*ppConsumerProvider)
                (*ppConsumerProvider)->Release();
            if(*ppConsumerProviderEx)
                (*ppConsumerProviderEx)->Release();

            // Use the cached one
            // ==================

            *ppSink = m_pSink;
            if(m_pSink)
                m_pSink->AddRef();

            *ppConsumerProvider = m_pConsumerProvider;
            if(m_pConsumerProvider)
                m_pConsumerProvider->AddRef();

            *ppConsumerProviderEx = m_pConsumerProviderEx;
            if(m_pConsumerProviderEx)
                m_pConsumerProviderEx->AddRef();
        }
        else
        {
            // Cache it
            // ========

            m_pSink = *ppSink;
            if(m_pSink)
                m_pSink->AddRef();

            m_pConsumerProvider = *ppConsumerProvider;
            if(m_pConsumerProvider)
                m_pConsumerProvider->AddRef();

            m_pConsumerProviderEx = *ppConsumerProviderEx;
            if(m_pConsumerProviderEx)
                m_pConsumerProviderEx->AddRef();
    
            m_bResolved = TRUE;
        }
    }

    return S_OK;
}

void CConsumerProviderRecord::FireNCSinkEvent(
    DWORD dwIndex, 
    IWbemClassObject *pLogicalConsumer)
{
    if (IS_NCEVENT_ACTIVE(dwIndex))
    {
        // Get the path of the logical consumer.
        VARIANT vPath;
        BSTR    strLogicalConsumerPath;
            
        VariantInit(&vPath);

        if (pLogicalConsumer && 
            SUCCEEDED(pLogicalConsumer->Get(L"__PATH", 0, &vPath, NULL, NULL)))
            strLogicalConsumerPath = V_BSTR(&vPath);
        else
            strLogicalConsumerPath = NULL;

        //
        // Report the event.
        //
        FIRE_NCEVENT(
            g_hNCEvents[dwIndex], 
            WMI_SENDCOMMIT_SET_NOT_REQUIRED,

            // Data follows...
            m_pNamespace->GetName(),
            m_wszProviderName,
            m_wszMachineName,
            strLogicalConsumerPath);
            
        VariantClear(&vPath);
    }
}

HRESULT CConsumerProviderRecord::FindConsumer(
                IN IWbemClassObject* pLogicalConsumer,
                OUT IWbemUnboundObjectSink** ppSink)
{
    HRESULT hres;

    // Check if consumer provider is cached
    // ====================================

    IWbemEventConsumerProvider* pConsumerProvider = NULL;
    BOOL bResolved = FALSE;
    
    {
        CInCritSec ics(&m_cs);
        m_LastAccess = CWbemTime::GetCurrentTime();

        if(m_bResolved)
        {
            pConsumerProvider = m_pConsumerProvider;
            if(pConsumerProvider)
                pConsumerProvider->AddRef();

            bResolved = TRUE;
        }
    }

    // Resolve if not cached
    // =====================

    if(!bResolved)
    {
        IWbemUnboundObjectSink* pGlobalSink;
        IWbemEventConsumerProviderEx* pConsumerProviderEx = NULL;

        hres = ResolveAndCache(&pGlobalSink, &pConsumerProvider, 
                                    &pConsumerProviderEx);
        if(FAILED(hres)) return hres;

        if(pGlobalSink)
            pGlobalSink->Release();
        if(pConsumerProviderEx)
            pConsumerProviderEx->Release();
    }

    if(pConsumerProvider == NULL)
        return E_NOINTERFACE;

    try
    {
        hres = pConsumerProvider->FindConsumer(pLogicalConsumer, ppSink);
    }
    catch(...)
    {
        ERRORTRACE((LOG_ESS, "Event consumer provider %S in namespace %S "
            "threw an exception in FindConsumer\n", 
                m_wszProviderName, m_pNamespace->GetName()));
        hres = WBEM_E_PROVIDER_FAILURE;
    }

    if(SUCCEEDED(hres) && ppSink != NULL)
    {
        if(*ppSink == NULL)
        {
            ERRORTRACE((LOG_ESS, "Event consumer provider %S in namespace %S "
                "returned success from IWbemEventConsumerProvider::FindConsumer"
                " call while returning a NULL sink.  This behavior is invalid! "
                " Consumers will not receive events.\n", 
                m_wszProviderName, m_pNamespace->GetName()));
            return E_NOINTERFACE;
        }

        //
        // Report the MSFT_WmiConsumerProviderSinkLoaded event.
        //
        FireNCSinkEvent(
            MSFT_WmiConsumerProviderSinkLoaded,
            pLogicalConsumer);


        // Configure proxy settings
        // ========================

        if(m_bAnonymous)
        {
            hres = SetInterfaceSecurity(*ppSink, NULL, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);
        }
        else
        {
            hres = WbemSetDynamicCloaking(*ppSink, RPC_C_AUTHN_LEVEL_CONNECT,
                        RPC_C_IMP_LEVEL_IDENTIFY);
        }
        if(FAILED(hres))
            return hres;
        else
            hres = WBEM_S_NO_ERROR;

    }
    pConsumerProvider->Release();

    return hres;
}

HRESULT CConsumerProviderRecord::Resolve(
                            IWbemUnboundObjectSink** ppSink,
                            IWbemEventConsumerProvider** ppConsumerProvider,
                            IWbemEventConsumerProviderEx** ppConsumerProviderEx)
{
    HRESULT hres;

    // Prepare for CoCreateInstance(Ex)
    // ================================

    COSERVERINFO* pServerInfo = NULL;
    DWORD dwClsCtx;
    if(m_wszMachineName)
    {
        pServerInfo = _new COSERVERINFO;
        if(pServerInfo == NULL)
            return WBEM_E_OUT_OF_MEMORY;
        pServerInfo->pwszName = m_wszMachineName;
        pServerInfo->pAuthInfo = NULL;
        pServerInfo->dwReserved1 = 0;
        pServerInfo->dwReserved2 = 0;
        dwClsCtx = CLSCTX_REMOTE_SERVER | CLSCTX_LOCAL_SERVER;
    }
    else
    {
        dwClsCtx = CLSCTX_ALL;
    }

    CDeleteMe<COSERVERINFO> dm(pServerInfo);
    
    IUnknown* pProtoSink = NULL;
    if(m_wszMachineName)
    {
        //
        // Remote activation --- do everything ourselves
        //

        IClassFactory* pFactory;
        hres = WbemCoGetClassObject(m_clsid, dwClsCtx, pServerInfo,
                                IID_IClassFactory, (void**)&pFactory);
        if(FAILED(hres))
        {
            ERRORTRACE((LOG_ESS, 
                "Failed to get a class factory for CLSID on server %S.  "
                        "Return code %X\n",
                (pServerInfo?pServerInfo->pwszName:L"(local)"), hres));
            return hres;
        }
        CReleaseMe rm0(pFactory);
    
        if(pFactory == NULL)
        {
            ERRORTRACE((LOG_ESS, "NULL Class Factory received from event consumer "
                "%S.  Consumer needs to have its code examined\n", 
                m_wszProviderName));
    
            return WBEM_E_PROVIDER_LOAD_FAILURE;
        }
                
        // Get the instance
        // ================
    
        hres = pFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pProtoSink);
        if(FAILED(hres)) 
        {
            //
            // Try again at lower security
            //
            
            SetInterfaceSecurity(pFactory, NULL, NULL, NULL,
                            RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);
            hres = pFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pProtoSink);
            if(SUCCEEDED(hres))
                m_bAnonymous = TRUE;
        }
        if(FAILED(hres)) 
        {
            ERRORTRACE((LOG_ESS, 
                "Failed to create an instance from a class factory for %S. "
                " Return code: %X\n", m_wszProviderName, hres));
            return hres;
        }
        if(pProtoSink == NULL)
        {
            ERRORTRACE((LOG_ESS, "NULL object received from event consumer "
                "%S factory.  Consumer needs to have its code examined\n", 
                m_wszProviderName));
    
            return WBEM_E_PROVIDER_LOAD_FAILURE;
        }
    }
    else // not REMOTE_SERVER
    {
        //
        // Use PSS
        //

        hres = m_pNamespace->LoadConsumerProvider(m_wszProviderName, 
                                    &pProtoSink);
        if(FAILED(hres))
        {
            ERRORTRACE((LOG_ESS, "ESS unable to load consumer provider %S from "
                            "provider subsystem: 0x%X\n", 
                        (LPCWSTR)m_wszProviderName, hres));
            return hres;
        }
    }

    CReleaseMe rm1(pProtoSink);

    if(m_bAnonymous)
        SetInterfaceSecurity(pProtoSink, NULL, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);
        

    // Query for the interfaces
    // ========================

    *ppSink = NULL;
    hres = pProtoSink->QueryInterface(IID_IWbemUnboundObjectSink, 
                            (void**)ppSink);
    if(FAILED(hres))
    {
        DEBUGTRACE((LOG_ESS, 
            "Consumer provider %S does not support "
                    "IWbemUnboundObjectSink: error code %X\n", 
                        m_wszProviderName, hres));
    }
    else
    {
        if(*ppSink == NULL)
        {
            ERRORTRACE((LOG_ESS, "NULL object received from event consumer "
                "%S QueryInterface. Consumer needs to have its code examined\n",
                m_wszProviderName));
    
            return WBEM_E_PROVIDER_LOAD_FAILURE;
        }

        // Configure proxy settings
        // ========================

        if(m_bAnonymous)
        {
            hres = SetInterfaceSecurity(*ppSink, NULL, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);
        }
        else
        {
            hres = WbemSetDynamicCloaking(*ppSink, RPC_C_AUTHN_LEVEL_CONNECT,
                        RPC_C_IMP_LEVEL_IDENTIFY);
        }
        if(FAILED(hres))
            return hres;
    }

    *ppConsumerProvider = NULL;
    hres = pProtoSink->QueryInterface(IID_IWbemEventConsumerProvider, 
                            (void**)ppConsumerProvider);
    if(FAILED(hres))
    {
    }
    else if(*ppConsumerProvider == NULL)
    {
        ERRORTRACE((LOG_ESS, "NULL object received from event consumer "
            "%S QueryInterface.  Consumer needs to have its code examined\n", 
            m_wszProviderName));

        return WBEM_E_PROVIDER_LOAD_FAILURE;
    }
    else
    {
        if(m_bAnonymous)
            SetInterfaceSecurity(*ppConsumerProvider, NULL, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);
    }

    *ppConsumerProviderEx = NULL;
    hres = pProtoSink->QueryInterface(IID_IWbemEventConsumerProviderEx, 
                            (void**)ppConsumerProviderEx);
    if(FAILED(hres))
    {
    }
    else if(*ppConsumerProviderEx == NULL)
    {
        ERRORTRACE((LOG_ESS, "NULL object received from event consumer "
            "%S QueryInterface.  Consumer needs to have its code examined\n", 
            m_wszProviderName));

        return WBEM_E_PROVIDER_LOAD_FAILURE;
    }
    else
    {
        if(m_bAnonymous)
            SetInterfaceSecurity(*ppConsumerProviderEx, NULL, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);
    }

#if 0
    // Check if initialization is desired
    // ==================================

    IWbemProviderInit* pInit;
    if(SUCCEEDED(pProtoSink->QueryInterface(IID_IWbemProviderInit, 
                                            (void**)&pInit)))
    {
        if(pInit == NULL)
        {
            ERRORTRACE((LOG_ESS, "NULL object received from event consumer "
                "%S factory.  Consumer needs to have its code examined\n", 
                m_wszProviderName));
    
            return WBEM_E_PROVIDER_LOAD_FAILURE;
        }
    
        if(m_bAnonymous)
            SetInterfaceSecurity(pInit, NULL, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS);

        CReleaseMe rm2(pInit);

        CProviderInitSink* pSink = new CProviderInitSink;
        if(pSink == NULL)
            return WBEM_E_OUT_OF_MEMORY;

        pSink->AddRef();
        CReleaseMe rm3(pSink);

        // Retrieve a namespace pointer suitable for providers
        // ===================================================

        IWbemServices* pServices = NULL;
        hres = m_pNamespace->GetProviderNamespacePointer(&pServices);
        if(FAILED(hres))
            return hres;
        CReleaseMe rm4(pServices);

        hres = pInit->Initialize(NULL, 0, (LPWSTR)m_pNamespace->GetName(), 
                            NULL, pServices, NULL, pSink);
        if(FAILED(hres))
        {
            ERRORTRACE((LOG_ESS, "Event consumer provider %S failed to "
                                "initialize: 0x%X\n", m_wszProviderName,
                                                            hres));
            return hres;
        }
    
        hres = pSink->WaitForCompletion();
        if(FAILED(hres))
        {
            ERRORTRACE((LOG_ESS, "Event consumer provider %S failed to "
                                "initialize: 0x%X\n", m_wszProviderName, hres));
            return hres;
        }
    }

#endif
   
    // Inform the cache that unloading may be required
    // ===============================================

    m_pNamespace->GetConsumerProviderCache().EnsureUnloadInstruction();

    //
    // Report the MSFT_WmiConsumerProviderLoaded event.
    //
    FIRE_NCEVENT(
        g_hNCEvents[MSFT_WmiConsumerProviderLoaded], 
        WMI_SENDCOMMIT_SET_NOT_REQUIRED,

        // Data follows...
        m_pNamespace->GetName(),
        m_wszProviderName,
        m_wszMachineName);

    return S_OK;
}
    


CConsumerProviderCache::~CConsumerProviderCache()
{
    if(m_pInstruction)
    {
        m_pInstruction->Terminate();
        m_pInstruction->Release();
    }
}

BOOL CConsumerProviderCache::DoesContain(IWbemClassObject* pProvReg, 
                                            IWbemClassObject* pConsumerReg)
{
    HRESULT hres;

    // Get its class list
    // ==================

    VARIANT v;
    VariantInit(&v);
    CClearMe cm(&v);

    hres = pProvReg->Get(L"ConsumerClassNames", 0, &v, NULL, NULL);

    if(SUCCEEDED(hres) && V_VT(&v) == (VT_BSTR | VT_ARRAY))
    {
        SAFEARRAY* psa = V_ARRAY(&v);
        long lLBound, lUBound;
        BSTR* astrData;
        SafeArrayGetLBound(psa, 1, &lLBound);
        SafeArrayGetUBound(psa, 1, &lUBound);
        SafeArrayAccessData(psa, (void**)&astrData);
        CUnaccessMe um(psa);
        
        for(int i = 0; i <= lUBound - lLBound; i++)
        {
            if(pConsumerReg->InheritsFrom(astrData[i]) == S_OK)
                return TRUE;
        }
    }

    return FALSE;
}
            
//
// Need a class for dynamic enumeration of consumer provider registrations
//

class CProviderRegistrationSink : public CObjectSink
{
protected:
    CConsumerProviderCache* m_pCache;
    IWbemClassObject* m_pLogicalConsumer;
    IWbemClassObject** m_ppReg;
public:
    CProviderRegistrationSink(CConsumerProviderCache* pCache, 
        IWbemClassObject* pLogicalConsumer, IWbemClassObject** ppReg) : 
            m_pCache(pCache), m_pLogicalConsumer(pLogicalConsumer),
            m_ppReg(ppReg)
    {
        AddRef();
        // same thread --- no need to AddRef paramters
    }
    ~CProviderRegistrationSink(){}
    STDMETHOD(Indicate)(long lNumObjects, IWbemClassObject** apObjects)
    {
        for(long i = 0; i < lNumObjects; i++)
        {
            //
            // Check if this one is ours
            //

            if(m_pCache->DoesContain(apObjects[i], m_pLogicalConsumer))
            {
                *m_ppReg = apObjects[i];
                (*m_ppReg)->AddRef();
                return WBEM_E_CALL_CANCELLED;
            }
        }
        return WBEM_S_NO_ERROR;
    }
};


INTERNAL CConsumerProviderRecord*
CConsumerProviderCache::GetRecord(IN IWbemClassObject* pLogicalConsumer)
{
    CInCritSec ics(&m_cs);
    HRESULT hres;

    //
    // Enumerate all the registrations into a sink that will check if this
    // one is the right one
    //


    IWbemClassObject* pReg = NULL;
    CProviderRegistrationSink Sink(this, pLogicalConsumer, &pReg);

    hres = m_pNamespace->CreateInstanceEnum(
        CONSUMER_PROVIDER_REGISTRATION_CLASS, 0, &Sink);

    if(pReg == NULL)
    {
        // Not found
        return NULL;
    }
    
    CReleaseMe rm1(pReg);

    // Get the Win32Provider record
    // ============================

    VARIANT vPath;
    hres = pReg->Get(CONSPROV_PROVIDER_REF_PROPNAME, 0, &vPath, NULL, NULL);
    if(FAILED(hres) || V_VT(&vPath) != VT_BSTR)
    {
        ERRORTRACE((LOG_ESS, "Event consumer provider registration is invalid: "
                                "Provider property is missing\n"));
        return NULL;
    }

    INTERNAL BSTR strProviderRef = V_BSTR(&vPath);
    CClearMe cm2(&vPath);

    _IWmiObject* pProv = NULL;
    hres = m_pNamespace->GetInstance(V_BSTR(&vPath), &pProv);
    if(FAILED(hres))
    {
        ERRORTRACE((LOG_ESS, "Invalid event consumer provider registration: "
                                "dangling provider reference %S\n", 
                                    V_BSTR(&vPath)));
        return NULL;
    }

    CReleaseMe rm(pProv);

    // Get the name of the provider
    // ============================

    VARIANT vProvName;
    VariantInit(&vProvName);
    CClearMe cm3(&vProvName);

    hres = pProv->Get(PROVIDER_NAME_PROPNAME, 0, &vProvName, NULL, NULL);
    if(FAILED(hres) || V_VT(&vProvName) != VT_BSTR)
    {
        ERRORTRACE((LOG_ESS, "Event provider registration without a name at "
                        "%S\n", V_BSTR(&vPath)));
        return NULL;
    }
    INTERNAL BSTR strProviderName = V_BSTR(&vProvName);

    // Get the machine name 
    // ====================

    VARIANT vMachine;
    VariantInit(&vMachine);
    CClearMe cm4(&vMachine);

    hres = pLogicalConsumer->Get(CONSUMER_MACHINE_NAME_PROPNAME, 0, &vMachine, 
                                    NULL, NULL);
    if(FAILED(hres)) return NULL;

    INTERNAL BSTR strMachineName = NULL;
    if(V_VT(&vMachine) != VT_NULL)
        strMachineName = V_BSTR(&vMachine);

    // Search for the record
    // =====================

    BOOL bFound = FALSE;
    CConsumerProviderRecord* pRecord;
    for(int i = 0; i < m_apRecords.GetSize(); i++)
    {
        pRecord = m_apRecords[i];

        if(_wcsicmp(pRecord->GetProviderName(), strProviderName))
            continue;
        if(pRecord->GetMachineName() && strMachineName)
        {
            if(_wcsicmp(pRecord->GetMachineName(), strMachineName))
                continue;
        }
        else
        {
            if(pRecord->GetMachineName() != strMachineName)
                continue;
        }

        bFound = TRUE;
        break;
    }

    if(!bFound)
    {
        pRecord = new CConsumerProviderRecord(m_pNamespace);
        if(pRecord == NULL)
            return NULL;
        hres = pRecord->Initialize(pProv, strProviderRef, strProviderName, 
                                    strMachineName);
        if(m_apRecords.Add(pRecord) < 0)
        {
            delete pRecord;
            return NULL;
        }
    }

    pRecord->AddRef();
    return pRecord;
}

void CConsumerProviderCache::EnsureUnloadInstruction()
{
    CInCritSec ics(&m_cs);

    if(m_pInstruction == NULL)
    {
        m_pInstruction = new CConsumerProviderWatchInstruction(this);
        if(m_pInstruction)
        {
            m_pInstruction->AddRef();
            m_pNamespace->GetTimerGenerator().Set(m_pInstruction);
        }
    }
}

    
HRESULT CConsumerProviderCache::UnloadUnusedProviders(CWbemInterval Interval)
{
    CRefedPointerArray<CConsumerProviderRecord> apToInvalidate;
    BOOL bUnloaded = FALSE;

    {
        CInCritSec ics(&m_cs);
    
        BOOL bActiveLeft = FALSE;
        for(int i = 0; i < m_apRecords.GetSize(); i++)
        {
            CConsumerProviderRecord* pRecord = m_apRecords[i];
        
            // Prevent the record from being used while its fate is determined
            // ===============================================================
    
            if(pRecord->IsActive())
            {
                if(CWbemTime::GetCurrentTime() - pRecord->GetLastAccess() > 
                        Interval)
                {
                    apToInvalidate.Add(pRecord);
                    DEBUGTRACE((LOG_ESS, "Unloading consumer provider %S on "
                        "%S\n", pRecord->GetProviderName(), 
                                pRecord->GetMachineName()));
                    bUnloaded = TRUE;
                }
                else
                    bActiveLeft = TRUE;
            }
        }

        if(m_pInstruction && !bActiveLeft)
        {
            m_pInstruction->Terminate();
            m_pInstruction->Release();
            m_pInstruction = NULL;
        }
    }
    
    // Actually unload
    // ===============

    for(int i = 0; i < apToInvalidate.GetSize(); i++)
    {
        apToInvalidate[i]->Invalidate();
    }

    if(bUnloaded)
        m_pNamespace->GetTimerGenerator().ScheduleFreeUnusedLibraries();
    return WBEM_S_NO_ERROR;
}

HRESULT CConsumerProviderCache::RemoveConsumerProvider(LPCWSTR wszProviderRef)
{
    CInCritSec ics(&m_cs);
    for(int i = 0; i < m_apRecords.GetSize(); i++)
    {
        CConsumerProviderRecord* pRecord = m_apRecords[i];
    
        if(!_wcsicmp(pRecord->GetProviderRef(), wszProviderRef))
        {
            // Matches --- remove
            // ==================

            DEBUGTRACE((LOG_ESS, "Removing consumer provider record: %S in %S"
                "\n", m_pNamespace->GetName(), wszProviderRef));

            m_apRecords.RemoveAt(i);
            i--;
        }
    }

    return WBEM_S_NO_ERROR;
}

// static
SYSFREE_ME BSTR CConsumerProviderCache::GetProviderRefFromRecord(
                        IWbemClassObject* pReg)
{
    VARIANT v;
    VariantInit(&v);
    if(FAILED(pReg->Get(CONSPROV_PROVIDER_REF_PROPNAME, 0, &v, NULL, NULL)) || 
            V_VT(&v) != VT_BSTR)
    {
        VariantClear(&v);
        return NULL;
    }
    else
    {
        // Variant intentionally not cleared
        return V_BSTR(&v);
    }
}

class CSingleElementSink : public CObjectSink
{
protected:
    IWbemClassObject** m_ppObj;
public:
    CSingleElementSink(IWbemClassObject** ppObj) : m_ppObj(ppObj)
    {
        AddRef();
    }

    STDMETHOD(Indicate)(long lNumObjects, IWbemClassObject** apObjects)
    {
        if(lNumObjects > 0)
        {
            *m_ppObj = apObjects[0];
            apObjects[0]->AddRef();
        }
        return S_OK;
    }
};



HRESULT CConsumerProviderCache::GetConsumerProviderRegFromProviderReg(
                        IWbemClassObject* pProv, 
                        IWbemClassObject** ppConsProv)
{
    HRESULT hres;

    // Get the path
    // ============

    VARIANT vPath;
    VariantInit(&vPath);
    if(FAILED(pProv->Get(L"__RELPATH", 0, &vPath, NULL, NULL)) || 
             V_VT(&vPath) != VT_BSTR)
    {
        return WBEM_E_INVALID_PROVIDER_REGISTRATION;
    }
    
    WString wsPath = WString(V_BSTR(&vPath)).EscapeQuotes();
    VariantClear(&vPath);

    // Construct the query
    // ===================

    BSTR strQuery = 
        SysAllocStringLen(NULL, wsPath.Length()*2 + 100);
    CSysFreeMe sfm(strQuery);

    swprintf(strQuery, 
            L"select * from " CONSUMER_PROVIDER_REGISTRATION_CLASS L" where "
                L"Provider = \"%s\"",  (LPWSTR)wsPath);
    
    // Issue the query
    // ===============

    *ppConsProv = NULL;
    CSingleElementSink Sink(ppConsProv);
    hres = m_pNamespace->ExecQuery(strQuery, 0, &Sink);
    if(FAILED(hres))
        return hres;
    else if(*ppConsProv == NULL)
        return WBEM_E_NOT_FOUND;
    else 
        return WBEM_S_NO_ERROR;
}
    
void CConsumerProviderCache::Clear()
{
    CInCritSec ics(&m_cs);

    m_apRecords.RemoveAll();
    if(m_pInstruction)
    {
        m_pInstruction->Terminate();
        m_pInstruction->Release();
        m_pInstruction = NULL;
    }
}

    
void CConsumerProviderCache::DumpStatistics(FILE* f, long lFlags)
{
    fprintf(f, "%d consumer provider records\n", m_apRecords.GetSize());
}

// static 
CWbemInterval CConsumerProviderWatchInstruction::mstatic_Interval;
void CConsumerProviderWatchInstruction::staticInitialize(IWbemServices* pRoot)
{
    mstatic_Interval = 
            CBasicUnloadInstruction::staticRead(pRoot, GetCurrentEssContext(),
                                L"__EventConsumerProviderCacheControl=@");
}

CConsumerProviderWatchInstruction::CConsumerProviderWatchInstruction(
                                                CConsumerProviderCache* pCache)
    : CBasicUnloadInstruction(mstatic_Interval), m_pCache(pCache)
{}

HRESULT CConsumerProviderWatchInstruction::Fire(long, CWbemTime)
{
    if(!m_bTerminate)
    {
        CEssThreadObject Obj(NULL);
        SetConstructedEssThreadObject(&Obj);
    
        m_pCache->UnloadUnusedProviders(m_Interval);

        m_pCache->m_pNamespace->FirePostponedOperations();
        ClearCurrentEssThreadObject();
        return WBEM_S_NO_ERROR;
    }
    else
        return WBEM_S_FALSE;
}