//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997-2001.
//
//  File:       C O N M A N . C P P
//
//  Contents:   Connection manager.
//
//  Notes:
//
//  Author:     shaunco   21 Sep 1997
//
//----------------------------------------------------------------------------

#include "pch.h"
#pragma hdrstop
#include <dbt.h>
#include <ndisguid.h>
#include "conman.h"
#include "dialup.h"
#include "enum.h"
#include "enumw.h"
#include "ncnetcon.h"
#include "ncreg.h"
#include "nminit.h"
#if DBG
#include "ncras.h"
#endif // DBG
#include <wmium.h>
#include "cmutil.h"
#include <shlwapi.h>
#include <shfolder.h>
#include "cobase.h"
#define SECURITY_WIN32
#include <security.h>
#include <wzcsvc.h>

bool operator < (const GUID& rguid1, const GUID& rguid2)
{
    return memcmp(&rguid1, &rguid2, sizeof(GUID)) < 0;
}

static const WCHAR c_szRegKeyClassManagers [] = L"System\\CurrentControlSet\\Control\\Network\\Connections";
static const WCHAR c_szRegValClassManagers [] = L"ClassManagers";

volatile CConnectionManager* CConnectionManager::g_pConMan = NULL;
volatile BOOL                CConnectionManager::g_fInUse  = FALSE;

bool operator == (const NETCON_PROPERTIES& rProps1, const NETCON_PROPERTIES& rProps2)
{
    return (IsEqualGUID(rProps1.clsidThisObject, rProps2.clsidThisObject) &&
            IsEqualGUID(rProps1.clsidUiObject, rProps2.clsidUiObject) &&
            (rProps1.dwCharacter == rProps2.dwCharacter) &&
            IsEqualGUID(rProps1.guidId, rProps2.guidId) &&
            (rProps1.MediaType == rProps2.MediaType) &&
            (rProps1.pszwDeviceName == rProps2.pszwDeviceName) &&
            (rProps1.pszwName == rProps2.pszwName) &&
            (rProps1.Status == rProps2.Status));
}

const DWORD MAX_DISABLE_EVENT_TIMEOUT = 0xFFFF;

//static
BOOL
CConnectionManager::FHasActiveConnectionPoints ()
{
    BOOL fRet = FALSE;

    // Note our intent to use g_pConMan.  We may find out that it is not
    // available for use, but setting g_fInUse to TRUE prevents FinalRelease
    // from allowing the object to be destroyed while we are using it.
    //
    g_fInUse = TRUE;

    // Save g_pConMan into a local variable since we have to test and use
    // it atomically.  If we tested g_pConMan directly and then used it
    // directly, it may have been set to NULL by FinalRelease in between
    // our test and use.  (Uh, which would be bad.)
    //
    // The const_cast is because g_pConMan is declared volatile.
    //
    CConnectionManager* pConMan = const_cast<CConnectionManager*>(g_pConMan);
    if (pConMan)
    {
        pConMan->Lock();

        IUnknown** ppUnk;
        for (ppUnk = pConMan->m_vec.begin();
             ppUnk < pConMan->m_vec.end();
             ppUnk++)
        {
            if (ppUnk && *ppUnk)
            {
                fRet = TRUE;
                break;
            }
        }

        pConMan->Unlock();
    }

    // Now that we are finished using the object, indicate so.  FinalRelease
    // may be waiting for this condition in which case the object will soon
    // be destroyed.
    //
    g_fInUse = FALSE;

    return fRet;
}

//+---------------------------------------------------------------------------
//
//  Member:     CConnectionManager::FinalRelease
//
//  Purpose:    COM destructor
//
//  Arguments:
//      (none)
//
//  Returns:    nothing
//
//  Author:     shaunco   21 Sep 1997
//
//  Notes:
//
VOID
CConnectionManager::FinalRelease ()
{
    TraceFileFunc(ttidConman);

    if (m_hRegClassManagerKey)
    {
        RegCloseKey(m_hRegClassManagerKey);
        m_hRegClassManagerKey = NULL;
    }

    NTSTATUS Status = RtlDeregisterWait(m_hRegNotifyWait);
    if (!NT_SUCCESS(Status))
    {
        TraceError("Could not deregister Registry Change Notification", HrFromLastWin32Error());
    }
    m_hRegNotifyWait = NULL;

    if (m_hRegNotify)
    {
        CloseHandle(m_hRegNotify);
        m_hRegNotify = NULL;
    }

    // Unregister for PnP device events if we successfully registered for
    // them.
    //
    if (m_hDevNotify)
    {
        TraceTag (ttidConman, "Calling UnregisterDeviceNotification...");

        if (!UnregisterDeviceNotification (m_hDevNotify))
        {
            TraceHr (ttidError, FAL, HrFromLastWin32Error(), FALSE,
                "UnregisterDeviceNotification");
        }
    }

    (VOID) HrEnsureRegisteredOrDeregisteredWithWmi (FALSE);

    // Revoke the global connection manager pointer so that subsequent calls
    // to NotifyClientsOfEvent on other threads will do nothing.
    //
    g_pConMan = NULL;

    // Wait for g_fInUse to become FALSE.  NotifyClientsOfEvent will set
    // this to TRUE while it is using us.
    // Keep track of the number of times we sleep and trace it for
    // informational purposes.  If we see that we are waiting quite a few
    // number of times, increase the wait period.
    //
#ifdef ENABLETRACE
    if (g_fInUse)
    {
        TraceTag (ttidConman, "CConnectionManager::FinalRelease is waiting "
            "for NotifyClientsOfEvent to finish...");
    }
#endif

    ULONG cSleeps = 0;
    const DWORD nMilliseconds = 0;
    while (g_fInUse)
    {
        cSleeps++;
        Sleep (nMilliseconds);
    }

#ifdef ENABLETRACE
    if (cSleeps)
    {
        TraceTag (ttidConman, "CConnectionManager::FinalRelease slept %d "
            "times.  (%d ms each time.)",
            cSleeps, nMilliseconds);
    }
#endif

    // Release our class managers.
    //
    for (CLASSMANAGERMAP::iterator iter = m_mapClassManagers.begin(); iter != m_mapClassManagers.end(); iter++)
    {
        ReleaseObj (iter->second);
    }

    TraceTag (ttidConman, "Connection manager being destroyed");
}

inline
LPVOID OffsetToPointer(LPVOID pStart, DWORD dwNumBytes)
{
    DWORD_PTR dwPtr;

    dwPtr = reinterpret_cast<DWORD_PTR>(pStart);

    dwPtr += dwNumBytes;

    return reinterpret_cast<LPVOID>(dwPtr);
}

//
// Must match type of NOTIFICATIONCALLBACK
//
VOID
WINAPI
WmiEventCallback (
    PWNODE_HEADER Wnode,
    UINT_PTR NotificationContext)
{
    TraceTag (ttidConman,
        "WmiEventCallback called...");

    TraceTag(ttidEvents, "Flags: %d", Wnode->Flags);

    if (WNODE_FLAG_SINGLE_INSTANCE == (WNODE_FLAG_SINGLE_INSTANCE & Wnode->Flags))
    {
        PWNODE_SINGLE_INSTANCE pInstance = reinterpret_cast<PWNODE_SINGLE_INSTANCE>(Wnode);
        LPCWSTR lpszDevice = NULL;
        LPWSTR lpszGuid;
        GUID guidAdapter;

        lpszDevice = reinterpret_cast<LPCWSTR>(OffsetToPointer(pInstance, pInstance->DataBlockOffset));

        lpszGuid = wcsrchr(lpszDevice, L'{');

        TraceTag(ttidEvents, "Adapter Guid From NDIS for Media Status Change Event: %S", lpszGuid);
        if (SUCCEEDED(CLSIDFromString(lpszGuid, &guidAdapter)))
        {
            CONMAN_EVENT* pEvent;

            pEvent = new CONMAN_EVENT;

            if(pEvent)
            {
                pEvent->ConnectionManager = CONMAN_LAN;
                pEvent->guidId = guidAdapter;
                pEvent->Type = CONNECTION_STATUS_CHANGE;

                if (IsEqualGUID(Wnode->Guid, GUID_NDIS_STATUS_MEDIA_CONNECT))
                {
                    pEvent->Status = NCS_CONNECTED;
                }
                else if (IsEqualGUID(Wnode->Guid, GUID_NDIS_STATUS_MEDIA_DISCONNECT))
                {
                    pEvent->Status = NCS_MEDIA_DISCONNECTED;
                }
                else
                {
                    AssertSz(FALSE, "We never registered for this event ... WMI may be having internal issues.");
                    MemFree(pEvent);
                    return;
                }
                if (!QueueUserWorkItemInThread(LanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
                {
                    FreeConmanEvent(pEvent);
                }
            }
        }
    }
    else
    {
        LanEventNotify (REFRESH_ALL, NULL, NULL, NULL);
    }
}

HRESULT
CConnectionManager::HrEnsureRegisteredOrDeregisteredWithWmi (
    BOOL fRegister)
{
        // Already registered or deregistered?
    //
    if (!!m_fRegisteredWithWmi == !!fRegister)
    {
        return S_OK;
    }

    m_fRegisteredWithWmi = !!fRegister;

    HRESULT     hr = S_OK;
    DWORD       dwErr;
    INT         i;
    const GUID* apguid [] =
    {
        &GUID_NDIS_STATUS_MEDIA_CONNECT,
        &GUID_NDIS_STATUS_MEDIA_DISCONNECT,
    };

    TraceTag (ttidConman,
        "Calling WmiNotificationRegistration to %s for NDIS media events...",
        (fRegister) ? "register" : "unregister");

    for (i = 0; i < celems(apguid); i++)
    {
        dwErr = WmiNotificationRegistration (
                    const_cast<GUID*>(apguid[i]),
                    !!fRegister,    // !! for BOOL to BOOLEAN
                    WmiEventCallback,
                    0,
                    NOTIFICATION_CALLBACK_DIRECT);

        hr = HRESULT_FROM_WIN32 (dwErr);
        TraceHr (ttidError, FAL, hr, FALSE, "WmiNotificationRegistration");
    }

    TraceHr (ttidError, FAL, hr, FALSE, "HrEnsureRegisteredOrDeregisteredWithWmi");
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CConnectionManager::NotifyClientsOfEvent
//
//  Purpose:    Notify our connection points that this object has changed
//              state in some way and that a re-enumeration is needed.
//
//  Arguments:
//      (none)
//
//  Returns:    nothing
//
//  Author:     shaunco   20 Mar 1998
//
//  Notes:      This is a static function.  No this pointer is passed.
//
// static
VOID CConnectionManager::NotifyClientsOfEvent (
    CONMAN_EVENT* pEvent)
{
    HRESULT     hr;
    // Let's be sure we only do work if the service state is still running.
    // If we have a stop pending for example, we don't need to do anything.
    //
    if (SERVICE_RUNNING != _Module.DwServiceStatus ())
    {
        return;
    }

    // Note our intent to use g_pConMan.  We may find out that it is not
    // available for use, but setting g_fInUse to TRUE prevents FinalRelease
    // from allowing the object to be destroyed while we are using it.
    //
    g_fInUse = TRUE;

    // Save g_pConMan into a local variable since we have to test and use
    // it atomically.  If we tested g_pConMan directly and then used it
    // directly, it may have been set to NULL by FinalRelease in between
    // our test and use.  (Uh, which would be bad.)
    //
    // The const_cast is because g_pConMan is declared volatile.
    //
    CConnectionManager* pConMan = const_cast<CConnectionManager*>(g_pConMan);
    if (pConMan)
    {
        ULONG       cpUnk;
        IUnknown**  apUnk;

        hr = HrCopyIUnknownArrayWhileLocked (
                pConMan,
                &pConMan->m_vec,
                &cpUnk,
                &apUnk);

        if (SUCCEEDED(hr) && cpUnk && apUnk)
        {
#ifdef DBG
            CHAR szClientList[MAX_PATH];
            ZeroMemory(szClientList, MAX_PATH);
            LPSTR pszClientList = szClientList;

            ITERUSERNOTIFYMAP iter;
            for (iter = pConMan->m_mapNotify.begin(); iter != pConMan->m_mapNotify.end(); iter++)
            {
                pszClientList += sprintf(pszClientList, "%d ", iter->second->dwCookie);
                if (pszClientList > (szClientList + MAX_PATH-50) )
                {
                    break;
                }
            }

            if (iter != pConMan->m_mapNotify.end())
            {
                pszClientList += sprintf(pszClientList, "(more)");
            }

            TraceTag (ttidConman,
                "NotifyClientsOfEvent: Notifying %d clients. Cookies: %s)",
                cpUnk, szClientList);
#endif
            for (ULONG i = 0; i < cpUnk; i++)
            {
                INetConnectionNotifySink* pSink = NULL;
                BOOL                fFireEventOnSink = FALSE;

                hr = apUnk[i]->QueryInterface(IID_INetConnectionNotifySink, reinterpret_cast<LPVOID*>(&pSink));

                ReleaseObj(apUnk[i]);

                if (SUCCEEDED(hr))
                {
                    hr = CoSetProxyBlanket (
                        pSink,
                        RPC_C_AUTHN_WINNT,      // use NT default security
                        RPC_C_AUTHZ_NONE,       // use NT default authentication
                        NULL,                   // must be null if default
                        RPC_C_AUTHN_LEVEL_CALL, // call
                        RPC_C_IMP_LEVEL_IDENTIFY,
                        NULL,                   // use process token
                        EOAC_DEFAULT);

                    switch (pEvent->Type)
                    {
                        case CONNECTION_ADDED:

                            Assert (pEvent);
                            Assert (pEvent->pPropsEx);

                            TraceTag(ttidEvents, "Characteristics: %s", DbgNccf(pEvent->pPropsEx->dwCharacter));

                            if (!(NCCF_ALL_USERS == (pEvent->pPropsEx->dwCharacter & NCCF_ALL_USERS)))
                            {
                                const WCHAR* pchw = reinterpret_cast<const WCHAR*>(pEvent->pPropsEx->bstrPersistData);
                                const WCHAR* pchwMax;
                                PCWSTR       pszwPhonebook;
                                WCHAR LeadWord = PersistDataLead;
                                WCHAR TrailWord = PersistDataTrail;
                                IUnknown* pUnkSink = NULL;

                                hr = pSink->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnkSink));
                                AssertSz(SUCCEEDED(hr), "Please explain how this happened...");
                                if (SUCCEEDED(hr))
                                {
                                    // The last valid pointer for the embedded strings.
                                    //
                                    pchwMax = reinterpret_cast<const WCHAR*>(pEvent->pbPersistData + pEvent->cbPersistData
                                        - (sizeof (GUID) +
                                        sizeof (BOOL) +
                                        sizeof (TrailWord)));

                                    if (pchw && (LeadWord == *pchw))
                                    {
                                        TraceTag(ttidEvents, "Found Correct Lead Character.");
                                        // Skip past our lead byte.
                                        //
                                        pchw++;

                                        // Get PhoneBook path.  Search for the terminating null and make sure
                                        // we find it before the end of the buffer.  Using lstrlen to skip
                                        // the string can result in an an AV in the event the string is
                                        // not actually null-terminated.
                                        //
                                        for (pszwPhonebook = pchw; *pchw != L'\0' ; pchw++)
                                        {
                                            if (pchw >= pchwMax)
                                            {
                                                pszwPhonebook = NULL;
                                                break;
                                            }
                                        }

                                        TraceTag(ttidEvents, "Found Valid Phonebook: %S", (pszwPhonebook) ? L"TRUE" : L"FALSE");

                                        if (pszwPhonebook)
                                        {
                                            ITERUSERNOTIFYMAP iter = pConMan->m_mapNotify.find(pUnkSink);
                                            if (iter != pConMan->m_mapNotify.end())
                                            {
                                                tstring& strUserDataPath = iter->second->szUserProfilesPath;
                                                TraceTag(ttidEvents, "Comparing stored Path: %S to Phonebook Path: %S",
                                                         strUserDataPath.c_str(), pszwPhonebook);
                                                if (_wcsnicmp(pszwPhonebook, strUserDataPath.c_str(), strUserDataPath.length()) == 0)
                                                {
                                                    fFireEventOnSink = TRUE;
                                                }
                                            }
                                            else
                                            {
                                                TraceTag(ttidError, "Could not find Path for NotifySink: 0x%08x", pUnkSink);
                                            }
                                        }
                                    }
                                    else
                                    {
                                        // Some other devices do not use this Format, but need to be sent events.
                                        fFireEventOnSink = TRUE;
                                    }

                                    ReleaseObj(pUnkSink);
                                }
                            }
                            else
                            {
                                TraceTag(ttidEvents, "All User Connection");
                                fFireEventOnSink = TRUE;
                            }

                            if (fFireEventOnSink)
                            {
                                TraceTag (ttidEvents,
                                    "Notifying ConnectionAdded... (pSink=0x%p)",
                                    pSink);

                                hr = pSink->ConnectionAdded (
                                        pEvent->pPropsEx);
                            }

                            break;

                        case CONNECTION_BANDWIDTH_CHANGE:
                            TraceTag (ttidEvents,
                                "Notifying ConnectionBandWidthChange... (pSink=0x%p)",
                                pSink);

                            hr = pSink->ConnectionBandWidthChange (&pEvent->guidId);
                            break;

                        case CONNECTION_DELETED:
                            TraceTag (ttidEvents,
                                "Notifying ConnectionDeleted... (pSink=0x%p)",
                                pSink);

                            hr = pSink->ConnectionDeleted (&pEvent->guidId);
                            break;

                        case CONNECTION_MODIFIED:
                            Assert (pEvent->pPropsEx);

                            TraceTag (ttidEvents,
                                "Notifying ConnectionModified... (pSink=0x%p)",
                                pSink);

                            hr = pSink->ConnectionModified (pEvent->pPropsEx);

                            break;

                        case CONNECTION_RENAMED:
                            TraceTag (ttidEvents,
                                "Notifying ConnectionRenamed... (pSink=0x%p)",
                                pSink);

                            hr = pSink->ConnectionRenamed (&pEvent->guidId,
                                    pEvent->szNewName);
                        break;

                        case CONNECTION_STATUS_CHANGE:
                            TraceTag (ttidEvents,
                                "Notifying ConnectionStatusChange... (pSink=0x%p)",
                                pSink);

                            TraceTag(ttidEvents, "Status changed to: %s", DbgNcs(pEvent->Status));

                            hr = pSink->ConnectionStatusChange (&pEvent->guidId,
                                        pEvent->Status);
                            break;

                        case REFRESH_ALL:
                            TraceTag (ttidEvents,
                                "Notifying RefreshAll... (pSink=0x%p)",
                                pSink);

                            hr = pSink->RefreshAll ();
                            break;

                        case CONNECTION_ADDRESS_CHANGE:
                             TraceTag (ttidEvents,
                                "Notifying ConnectionAddressChange... (pSink=0x%p)",
                                pSink);

                             hr = pSink->ConnectionAddressChange(&pEvent->guidId);
                             break;

                        case CONNECTION_BALLOON_POPUP:
                            TraceTag (ttidEvents,
                                "Notifying ConnectionStatusChange... (pSink=0x%p)",
                                pSink);

                            hr = pSink->ShowBalloon(&pEvent->guidId, pEvent->szCookie, pEvent->szBalloonText);
                            break;

                        case DISABLE_EVENTS:
                            TraceTag (ttidEvents,
                                "Notifying DisableEvents... (pSink=0x%p)",
                                pSink);

                            hr = pSink->DisableEvents(pEvent->fDisable, pEvent->ulDisableTimeout);
                            break;

                        default:
                            TraceTag(ttidEvents, "Event Type Passed: %d", pEvent->Type);
                            AssertSz (FALSE, "Invalid Type specified in pEvent");
                            break;
                    }
                    TraceErrorOptional("pSink call failed: ", hr, (S_FALSE == hr) );

                    if ( (HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) == hr)  ||
                         (HRESULT_FROM_WIN32(RPC_S_CALL_FAILED_DNE) == hr)  ||
                         (RPC_E_SERVER_DIED  == hr) ||
                         (RPC_E_DISCONNECTED == hr) ||
                         (HRESULT_FROM_WIN32(RPC_S_CALL_FAILED) == hr) )
                    {
                        IUnknown* pUnkSink = NULL;
                        HRESULT hrT = pSink->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnkSink));
                        if (SUCCEEDED(hrT))
                        {
                            ITERUSERNOTIFYMAP iter = pConMan->m_mapNotify.find(pUnkSink);
                            if (iter != pConMan->m_mapNotify.end())
                            {
                                TraceTag(ttidError, "Dead client detected. Removing notify advise for: %S", iter->second->szUserName.c_str());

                                hrT = pConMan->Unadvise(iter->second->dwCookie);
                            }
                            ReleaseObj(pUnkSink);
                        }
                        TraceHr (ttidError, FAL, hrT, S_FALSE == hrT, "Error removing notify advise.");
                    }

                    ReleaseObj(pSink);
                }
            }
            MemFree (apUnk);
        }
    }


    // Now that we are finished using the object, indicate so.  FinalRelease
    // may be waiting for this condition in which case the object will soon
    // be destroyed.
    //

    g_fInUse = FALSE;

}

//+---------------------------------------------------------------------------
//
//  Member:     CConnectionManager::HrEnsureClassManagersLoaded
//
//  Purpose:    Loads the class managers if they have not been loaded yet.
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or an error code.
//
//  Author:     shaunco   10 Dec 1997
//
//  Notes:
//
HRESULT
CConnectionManager::HrEnsureClassManagersLoaded ()
{
    HRESULT hr = S_OK;

    // Need to protect m_mapClassManagers for the case that two clients
    // get our object simultaneously and call a method which invokes
    // this method.  Need to transition m_mapClassManagers from being
    // empty to being full in one atomic operation.
    //
    CExceptionSafeComObjectLock EsLock (this);

    // If our vector of class managers is emtpy, try to load them.
    // This will certainly be the case if we haven't tried to load them yet.
    // If will also be the case when no class managers are registered.
    // This isn't likely because we register them in hivesys.inf, but it
    // shouldn't hurt to keep trying if they're really are none registered.
    //
    if (m_mapClassManagers.empty())
    {
        TraceTag (ttidConman, "Loading class managers...");

        // Load the registered class managers.
        //

        // Open the registry key where the class managers are registered.
        //
        HKEY hkey;
        hr = HrRegOpenKeyEx (HKEY_LOCAL_MACHINE,
                        c_szRegKeyClassManagers, KEY_READ, &hkey);
        if (SUCCEEDED(hr))
        {
            // Read the multi-sz of class manager CLSIDs.
            //
            PWSTR pmsz;
            hr = HrRegQueryMultiSzWithAlloc (hkey, c_szRegValClassManagers,
                    &pmsz);
            if (S_OK == hr)
            {
                (VOID) HrNmWaitForClassObjectsToBeRegistered ();

                // For each CLSID, create the object and request its
                // INetConnectionManager interface.
                //
                for (PCWSTR pszClsid = pmsz;
                     *pszClsid;
                     pszClsid += wcslen (pszClsid) + 1)
                {
                    // Convert the string to a CLSID.  If it fails, skip it.
                    //
                    CLSID clsid;
                    if (FAILED(CLSIDFromString ((LPOLESTR)pszClsid, &clsid)))
                    {
                        TraceTag (ttidConman, "Skipping bogus CLSID (%S)",
                            pszClsid);
                        continue;
                    }

                    // Create the class manager and add it to our list.
                    //
                    INetConnectionManager* pConMan;

                    hr = CoCreateInstance (
                            clsid, NULL,
                            CLSCTX_ALL | CLSCTX_NO_CODE_DOWNLOAD,
                            IID_INetConnectionManager,
                            reinterpret_cast<VOID**>(&pConMan));

                    TraceHr (ttidError, FAL, hr, FALSE,
                        "CConnectionManager::HrEnsureClassManagersLoaded: "
                        "CoCreateInstance failed for class manager %S.",
                        pszClsid);

                    if (SUCCEEDED(hr))
                    {
                        TraceTag (ttidConman, "Loaded class manager %S",
                            pszClsid);

                        Assert (pConMan);

                        if (m_mapClassManagers.find(clsid) != m_mapClassManagers.end())
                        {
                            AssertSz(FALSE, "Attempting to insert the same class manager twice!");
                        }
                        else
                        {
                            m_mapClassManagers[clsid] = pConMan;
                        }
                    }

/*
// If CoCreateInstance starts failing again on retail builds, this can
// be helpful.
                    else
                    {
                        CHAR psznBuf [512];
                        wsprintfA (psznBuf, "NETCFG: CoCreateInstance failed "
                            "(0x%08x) on class manager %i.\n",
                            hr, m_mapClassManagers.size ());
                        OutputDebugStringA (psznBuf);
                    }
*/
                }

                MemFree (pmsz);
            }

            RegCloseKey (hkey);
        }

        TraceTag (ttidConman, "Loaded %i class managers",
            m_mapClassManagers.size ());
    }

    TraceErrorOptional ("CConnectionManager::HrEnsureClassManagersLoaded", hr, (S_FALSE == hr));
    return hr;
}

VOID NTAPI CConnectionManager::RegChangeNotifyHandler(IN LPVOID pContext, IN BOOLEAN fTimerFired)
{
    TraceTag(ttidConman, "CConnectionManager::RegChangeNotifyHandler (%d)", fTimerFired);

    CConnectionManager *pThis = reinterpret_cast<CConnectionManager *>(pContext);
    CExceptionSafeComObjectLock EsLock (pThis);

    list<GUID> lstRegisteredGuids;

    HKEY hkey;
    HRESULT hr = HrRegOpenKeyEx (HKEY_LOCAL_MACHINE, c_szRegKeyClassManagers, KEY_READ, &hkey);
    if (SUCCEEDED(hr))
    {
        // Read the multi-sz of class manager CLSIDs.
        //
        PWSTR pmsz;
        hr = HrRegQueryMultiSzWithAlloc (hkey, c_szRegValClassManagers,
                &pmsz);
        if (S_OK == hr)
        {
            for (PCWSTR pszClsid = pmsz;
                 *pszClsid;
                 pszClsid += wcslen (pszClsid) + 1)
            {
                CLSID clsid;
                if (FAILED(CLSIDFromString ((LPOLESTR)pszClsid, &clsid)))
                {
                    TraceTag (ttidConman, "Skipping bogus CLSID (%S)", pszClsid);
                    continue;
                }
                lstRegisteredGuids.push_back(clsid);
            }
        }
        RegCloseKey(hkey);


        BOOL bFound;
        do
        {
            bFound = FALSE;

            CLASSMANAGERMAP::iterator iterClassMgr;

            for (iterClassMgr = pThis->m_mapClassManagers.begin();  iterClassMgr != pThis->m_mapClassManagers.end(); iterClassMgr++)
            {
                if (find(lstRegisteredGuids.begin(), lstRegisteredGuids.end(), iterClassMgr->first) == lstRegisteredGuids.end())
                {
                    // Class manager key has been removed
                    TraceTag(ttidConman, "Removing class manager");
                    bFound = TRUE;
                    break;
                }
            }

            if (bFound)
            {
                ULONG uRefCount = iterClassMgr->second->Release();
                TraceTag(ttidConman, "Releasing class manager - Refcount = %d", uRefCount);
                pThis->m_mapClassManagers.erase(iterClassMgr);
            }
        } while (bFound);

        for (list<GUID>::iterator iter = lstRegisteredGuids.begin(); iter != lstRegisteredGuids.end(); iter++)
        {
            if (pThis->m_mapClassManagers.find(*iter) == pThis->m_mapClassManagers.end())
            {
                // Class manager key has been added
                TraceTag(ttidConman, "Adding class manager");

                INetConnectionManager* pConMan;
                hr = CoCreateInstance (
                        *iter,
                        NULL,
                        CLSCTX_ALL | CLSCTX_NO_CODE_DOWNLOAD,
                        IID_INetConnectionManager,
                        reinterpret_cast<VOID**>(&pConMan));

                TraceHr (ttidError, FAL, hr, FALSE,
                    "CConnectionManager::RegChangeNotifyHandler: CoCreateInstance failed for class manager.");

                if (SUCCEEDED(hr))
                {
                    TraceTag (ttidConman, "Loaded class manager");
                    Assert (pConMan);

                    if (pThis->m_mapClassManagers.find(*iter) != pThis->m_mapClassManagers.end())
                    {
                        AssertSz(FALSE, "Attempting to insert the same class manager twice!");
                    }
                    else
                    {
                        pThis->m_mapClassManagers[*iter] = pConMan;
                    }
                }
            }
        }
    }
    else
    {
        TraceError("Could not open registry key", HrFromLastWin32Error());
    }

    TraceError("RegChangeNotifyHandler", hr);

    LanEventNotify (REFRESH_ALL, NULL, NULL, NULL);

    RegNotifyChangeKeyValue(pThis->m_hRegClassManagerKey, FALSE, REG_NOTIFY_CHANGE_LAST_SET, pThis->m_hRegNotify, TRUE);
}

//+---------------------------------------------------------------------------
// INetConnectionManager
//

//+---------------------------------------------------------------------------
//
//  Member:     CConnectionManager::EnumConnections
//
//  Purpose:    Return an INetConnection enumerator.
//
//  Arguments:
//      Flags        [in]
//      ppEnum       [out]  The enumerator.
//
//  Returns:    S_OK or an error code.
//
//  Author:     shaunco   21 Sep 1997
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::EnumConnections (
        NETCONMGR_ENUM_FLAGS    Flags,
        IEnumNetConnection**    ppEnum)
{
    HRESULT hr = S_OK;
    {
        CExceptionSafeComObjectLock EsLock (this);

        Assert(FImplies(m_hRegNotify, m_hRegClassManagerKey));
        if (!m_hRegNotify)
        {
            m_hRegNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
            if (m_hRegNotify)
            {
                NTSTATUS Status = RtlRegisterWait(&m_hRegNotifyWait, m_hRegNotify, &CConnectionManager::RegChangeNotifyHandler, this, INFINITE, WT_EXECUTEDEFAULT);
                if (!NT_SUCCESS(Status))
                {
                    hr = HRESULT_FROM_NT(Status);
                }
                else
                {
                    hr = HrRegOpenKeyEx (HKEY_LOCAL_MACHINE, c_szRegKeyClassManagers, KEY_READ, &m_hRegClassManagerKey);
                    if (SUCCEEDED(hr))
                    {
                        hr = RegNotifyChangeKeyValue(m_hRegClassManagerKey, FALSE, REG_NOTIFY_CHANGE_LAST_SET, m_hRegNotify, TRUE);
                        if (FAILED(hr))
                        {
                            RegCloseKey(m_hRegClassManagerKey);
                            m_hRegClassManagerKey = NULL;
                        }
                    }

                    if (FAILED(hr))
                    {
                        Status = RtlDeregisterWait(m_hRegNotifyWait);
                        if (!NT_SUCCESS(Status))
                        {
                            hr = HRESULT_FROM_NT(Status);
                        }
                        m_hRegNotifyWait = NULL;
                    }
                }

                if (FAILED(hr))
                {
                    CloseHandle(m_hRegNotify);
                    m_hRegNotify = NULL;
                }
            }
            else
            {
                hr = HrFromLastWin32Error();
            }
        }

        if (SUCCEEDED(hr))
        {

            // Create and return the enumerator.
            //
            hr = HrEnsureClassManagersLoaded ();
        }
    }

    if(SUCCEEDED(hr))
    {
        hr = CConnectionManagerEnumConnection::CreateInstance (
                    Flags,
                    m_mapClassManagers,
                    IID_IEnumNetConnection,
                    reinterpret_cast<VOID**>(ppEnum));
    }
    TraceErrorOptional ("CConnectionManager::EnumConnections", hr, (S_FALSE == hr));
    return hr;
}

//+---------------------------------------------------------------------------
// INetConnectionRefresh
//
STDMETHODIMP
CConnectionManager::RefreshAll()
{
    LanEventNotify (REFRESH_ALL, NULL, NULL, NULL);
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::ConnectionAdded
//
//  Purpose:    Notifies event sinks that a new connection has been added.
//
//  Arguments:
//      pConnection [in]  INetConnection* for new connection.
//
//  Returns:    Standard HRESULT
//
//  Author:     deonb   22 Mar 2001
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::ConnectionAdded(
    IN INetConnection* pConnection)
{
    HRESULT hr = S_OK;
    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = CONNECTION_ADDED;

        hr = HrGetPropertiesExFromINetConnection(pConnection, &pEvent->pPropsEx);
        if (SUCCEEDED(hr))
        {
            if (QueueUserWorkItemInThread(ConmanEventWorkItem, pEvent, EVENTMGR_CONMAN))
            {
                return hr;
            }
            hr = E_FAIL;
        }
        FreeConmanEvent(pEvent);
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::ConnectionDeleted
//
//  Purpose:    Sends an event to notify of a connection being deleted.
//
//  Arguments:
//      pguidId   [in] GUID of the  connectoid
//
//  Returns:    Standard HRESULT
//
//  Author:     ckotze   18 Apr 2001
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::ConnectionDeleted(
    IN const GUID* pguidId)
{
    HRESULT hr = S_OK;

    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = CONNECTION_DELETED;
        pEvent->guidId = *pguidId;

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            hr = E_FAIL;
            FreeConmanEvent(pEvent);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}


//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::ConnectionRenamed
//
//  Purpose:    Notifies the event sinks of a connection being renamed.
//
//  Arguments:
//      pConnection [in]  INetConnection* for new connection.
//
//  Returns:    Standard HRESULT
//
//  Author:     ckotze   19 Apr 2001
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::ConnectionRenamed(
    IN INetConnection* pConnection)
{
    HRESULT hr = S_OK;
    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = CONNECTION_RENAMED;

        hr = HrGetPropertiesExFromINetConnection(pConnection, &pEvent->pPropsEx);
        if (SUCCEEDED(hr))
        {
            lstrcpynW (pEvent->szNewName, pEvent->pPropsEx->bstrName, celems(pEvent->szNewName) );
            pEvent->guidId = pEvent->pPropsEx->guidId;

            if (QueueUserWorkItemInThread(ConmanEventWorkItem, pEvent, EVENTMGR_CONMAN))
            {
                return hr;
            }
            hr = E_FAIL;
        }
        FreeConmanEvent(pEvent);
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::ConnectionModified
//
//  Purpose:    Sends an event to notify clients that the connection has been
//              Modified.
//  Arguments:
//      pConnection [in]  INetConnection* for new connection.
//
//  Returns:    Standard HRESULT
//
//  Author:     ckotze   22 Mar 2001
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::ConnectionModified(INetConnection* pConnection)
{
    HRESULT hr = S_OK;
    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = CONNECTION_MODIFIED;

        hr = HrGetPropertiesExFromINetConnection(pConnection, &pEvent->pPropsEx);
        if (SUCCEEDED(hr))
        {
            if (QueueUserWorkItemInThread(ConmanEventWorkItem, pEvent, EVENTMGR_CONMAN))
            {
                return hr;
            }
            hr = E_FAIL;
        }
        FreeConmanEvent(pEvent);
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::ConnectionStatusChanged
//
//  Purpose:    Sends the ShowBalloon event to each applicable netshell
//
//  Arguments:
//      pguidId   [in] GUID of the  connectoid
//      ncs       [in] New status of the connectoid
//
//  Returns:    Standard HRESULT
//
//  Author:     deonb   22 Mar 2001
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::ConnectionStatusChanged(
                     IN const GUID* pguidId,
                     IN const NETCON_STATUS  ncs)
{
    HRESULT hr = S_OK;

    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = CONNECTION_STATUS_CHANGE;
        pEvent->guidId = *pguidId;
        pEvent->Status = ncs;

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            FreeConmanEvent(pEvent);
            hr = E_FAIL;
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::ShowBalloon
//
//  Purpose:    Sends the ShowBalloon event to each applicable netshell
//
//  Arguments:
//      pguidId       [in] GUID of the  connectoid
//      szCookie      [in] A specified cookie that will end up at netshell
//      szBalloonText [in] The balloon text
//
//  Returns:    Standard HRESULT
//
//  Author:     deonb   22 Mar 2001
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::ShowBalloon(
                     IN const GUID *pguidId,
                     IN const BSTR szCookie,
                     IN const BSTR szBalloonText)
{
    HRESULT hr = S_OK;

    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = CONNECTION_BALLOON_POPUP;
        pEvent->guidId        = *pguidId;
        pEvent->szCookie      = SysAllocStringByteLen(reinterpret_cast<LPCSTR>(szCookie), SysStringByteLen(szCookie));
        pEvent->szBalloonText = SysAllocString(szBalloonText);

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            hr = E_FAIL;
            FreeConmanEvent(pEvent);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::DisableEvents
//
//  Purpose:    Disable netshell processing of events for x milliseconds
//
//  Arguments:
//      fDisable         [in] TRUE to disable EVENT processing, FALSE to renable
//      ulDisableTimeout [in] Number of milliseconds to disable event processing for
//
//  Returns:    Standard HRESULT
//
//  Author:     deonb   10 April 2001
//
//  Notes:
//
STDMETHODIMP CConnectionManager::DisableEvents(IN const BOOL fDisable, IN const ULONG ulDisableTimeout)
{
    HRESULT hr = S_OK;

    if (ulDisableTimeout > MAX_DISABLE_EVENT_TIMEOUT)
    {
        return E_INVALIDARG;
    }

    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = DISABLE_EVENTS;
        pEvent->fDisable         = fDisable;
        // We set the high bit to let netshell know that this is coming from our private interface.
        pEvent->ulDisableTimeout = 0x80000000 | ulDisableTimeout;

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            hr = E_FAIL;
            FreeConmanEvent(pEvent);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::RefreshConnections
//
//  Purpose:    Refreshes the connections in the connections folder
//
//  Arguments:
//      None.
//
//
//  Returns:    Standard HRESULT
//
//  Author:     ckotze   19 April 2001
//
//  Notes:      This is a public interface that we are providing in order to
//              allow other components/companies to have some control over
//              the Connections Folder.
STDMETHODIMP CConnectionManager::RefreshConnections()
{
    HRESULT hr = S_OK;
    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = REFRESH_ALL;

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            hr = E_FAIL;
            FreeConmanEvent(pEvent);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::Enable
//
//  Purpose:    Enables events to be fired once more in the connections folder
//
//  Arguments:
//      None.
//
//  Returns:    Standard HRESULT
//
//  Author:     ckotze   19 April 2001
//
//  Notes:      This is a public interface that we are providing in order to
//              allow other components/companies to have some control over
//              the Connections Folder.
//              Since this is a public interface, we are only allowing the
//              INVALID_ADDRESS notification to be disabled
//              (this will take place in netshell.dll).
//
STDMETHODIMP CConnectionManager::Enable()
{
    HRESULT hr = S_OK;
    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type = DISABLE_EVENTS;
        pEvent->fDisable = FALSE;

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            hr = E_FAIL;
            FreeConmanEvent(pEvent);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   CConnectionManager::Disable
//
//  Purpose:    Disable netshell processing of events for x milliseconds
//
//  Arguments:
//      ulDisableTimeout [in] Number of milliseconds to disable event
//                            processing for.  Max is 60000 (1 minute)
//
//  Returns:    Standard HRESULT
//
//  Author:     ckotze   19 April 2001
//
//  Notes:      This is a public interface that we are providing in order to
//              allow other components/companies to have some control over
//              the Connections Folder.
//              Since this is a public interface, we are only allowing the
//              INVALID_ADDRESS notification to be disabled
//              (this will take place in netshell.dll).
//
STDMETHODIMP CConnectionManager::Disable(IN const ULONG ulDisableTimeout)
{
    HRESULT hr = S_OK;

    if (ulDisableTimeout > MAX_DISABLE_EVENT_TIMEOUT)
    {
        return E_INVALIDARG;
    }

    CONMAN_EVENT* pEvent = new CONMAN_EVENT;

    if (pEvent)
    {
        ZeroMemory(pEvent, sizeof(CONMAN_EVENT));
        pEvent->Type             = DISABLE_EVENTS;
        pEvent->fDisable         = TRUE;
        pEvent->ulDisableTimeout = ulDisableTimeout;

        if (!QueueUserWorkItemInThread(ConmanEventWorkItem, reinterpret_cast<PVOID>(pEvent), EVENTMGR_CONMAN))
        {
            hr = E_FAIL;
            FreeConmanEvent(pEvent);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

//+---------------------------------------------------------------------------
// INetConnectionCMUtil
//
//+---------------------------------------------------------------------------
//
//  Function:   MapCMHiddenConnectionToOwner
//
//  Purpose:    Maps a child connection to its parent connection.
//
//              Connection Manager has two stages: Dialup and VPN.
//              For the Dialup it creates a hidden connectoid that the
//              folder (netshell) does not see. However netman caches
//              the name, guid and status of this connectedoid. Both
//              the parent and child connectoid have the same name. When
//              the status of the hidden connectiod is updated the folder
//              recives the guid of the hidden connectoid and maps the
//              connectiod to it parent (Connection Manager) by searching
//              netmans cache for the name of the hidden connectoid. Then it
//              searches the connections in the folder for that name and thus
//              gets the guid of the parent connectoid.
//
//              When the folder gets a notify message from netman for the hidden
//              connection it uses this function to find the parent and update the
//              parent's status. The hidden connection is not displayed.
//
//  Arguments:
//      guidHidden   [in]   GUID of the hidden connectiod
//      pguidOwner   [out]  GUID of the parent connectiod
//
//  Returns:    S_OK -- Found hidden connection
//              else not found
//
//  Author:     omiller   1 Jun 2000
//
//  Notes:
//
STDMETHODIMP
CConnectionManager::MapCMHiddenConnectionToOwner (
    /*[in]*/  REFGUID guidHidden,
    /*[out]*/ GUID * pguidOwner)
{

    HRESULT hr = S_OK;
    bool bFound = false;

    hr = HrEnsureClassManagersLoaded ();
    if (SUCCEEDED(hr))
    {
        // Impersonate the user so that we see all of the connections displayed in the folder
        //
        hr = CoImpersonateClient();
        if (SUCCEEDED(hr))
        {

            // Find the hidden connection with this guid
            //
            CMEntry cm;

            hr = CCMUtil::Instance().HrGetEntry(guidHidden, cm);
            // Did we find the hidden connection?
            if( S_OK == hr )
            {
                // Enumerate through all of the connections and find connection with the same name as the
                // hidden connection
                //
                IEnumNetConnection * pEnum = NULL;

                hr = CWanConnectionManagerEnumConnection::CreateInstance(NCME_DEFAULT, IID_IEnumNetConnection, reinterpret_cast<LPVOID *>(&pEnum));

                if (SUCCEEDED(hr))
                {
                    INetConnection * pNetCon;
                    NETCON_PROPERTIES * pProps;
                    while(!bFound)
                    {
                        // Get the nect connection
                        //
                        hr = pEnum->Next(1, &pNetCon, NULL);
                        if(S_OK != hr)
                        {
                            // Ooops encountered some error, stop searching
                            //
                            break;
                        }

                        // Get the properties of the connection
                        //
                        hr = pNetCon->GetProperties(&pProps);
                        if(SUCCEEDED(hr))
                        {
                            if(lstrcmp(cm.m_szEntryName, pProps->pszwName) == 0)
                            {
                                // Found the connection that has the same name a the hidden connection
                                // Stop searching!!
                                *pguidOwner = pProps->guidId;
                                bFound = true;
                            }
                            FreeNetconProperties(pProps);
                        }
                        ReleaseObj(pNetCon);
                    }
                    ReleaseObj(pEnum);
                }
            }
            // Stop Impersonating the user
            //
            CoRevertToSelf();
        }
    }

    return bFound ? S_OK : S_FALSE;
}

//+---------------------------------------------------------------------------
// IConnectionPoint overrides  netman!CConnectionManager__Advise
//
STDMETHODIMP
CConnectionManager::Advise (
    IUnknown* pUnkSink,
    DWORD* pdwCookie)
{
    HRESULT hr = S_OK;

    TraceFileFunc(ttidConman);

    Assert(!FBadInPtr(pUnkSink));

    // Set our cloaked identity to be used when we call back on this
    // advise interface.  It's important to do this for security.  Since
    // we run as LocalSystem, if we were to call back without identify
    // impersonation only, the client could impersonate us and get a free
    // LocalSystem context which could be used for malicious purposes.
    //
    //
    CoSetProxyBlanket (
            pUnkSink,
            RPC_C_AUTHN_WINNT,      // use NT default security
            RPC_C_AUTHZ_NONE,       // use NT default authentication
            NULL,                   // must be null if default
            RPC_C_AUTHN_LEVEL_CALL, // call
            RPC_C_IMP_LEVEL_IDENTIFY,
            NULL,                   // use process token
            EOAC_DEFAULT);
    TraceHr(ttidError, FAL, hr, FALSE, "CoSetProxyBlanket");

    if (S_OK == hr)
    {
        // Initialize our event handler if it has not already been done.
        hr = HrEnsureEventHandlerInitialized();
        if (SUCCEEDED(hr))
        {
            // We still work for other events if this fails.
            hr = HrEnsureRegisteredWithNla();
            TraceErrorOptional("Could not register with Nla", hr, (S_FALSE == hr));

            // Call the underlying ATL implementation of Advise.
            //
            hr = IConnectionPointImpl<
                    CConnectionManager,
                    &IID_INetConnectionNotifySink>::Advise(pUnkSink, pdwCookie);

            TraceTag (ttidConman,
                "CConnectionManager::Advise called... pUnkSink=0x%p, cookie=%d",
                pUnkSink,
                *pdwCookie);

            TraceHr (ttidError, FAL, hr, FALSE, "IConnectionPointImpl::Advise");

            if (SUCCEEDED(hr))
            {
                WCHAR szUserAppData[MAX_PATH];
                HRESULT hrT = S_OK;
                HRESULT hrImpersonate = S_OK;

                if (SUCCEEDED(hrT))
                {
                    // We ignore this HRESULT because we can be called inproc and that would definitely fail.
                    // Instead we just make sure that it succeeded when befor call CoRevertToSelf.  This is
                    // fine because we'll still get a valid User App data path, it will just be something like
                    // LocalService or LocalSystem and we can still determine which sinks to send events to.

                    BOOLEAN     fNotifyWZC = FALSE;
                    WCHAR       szUserName[MAX_PATH];

                    CNotifySourceInfo* pNotifySourceInfo = new CNotifySourceInfo();
                    if (!pNotifySourceInfo)
                    {
                        hr = E_OUTOFMEMORY;
                    }
                    else
                    {
                        pNotifySourceInfo->dwCookie = *pdwCookie;

                        hrImpersonate = CoImpersonateClient();
                        if (SUCCEEDED(hrImpersonate) || (RPC_E_CALL_COMPLETE == hrImpersonate))
                        {
                            hrT = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, szUserAppData);
                            if (SUCCEEDED(hrT))
                            {
                                pNotifySourceInfo->szUserProfilesPath = szUserAppData;
                                TraceTag(ttidEvents, "Adding IUnknown for Sink to Map: 0x%08x.  Path: %S", reinterpret_cast<DWORD_PTR>(pUnkSink), szUserAppData);
                                TraceTag(ttidEvents, "Number of Items in Map: %d", m_mapNotify.size());
                            }
                            else
                            {
                                TraceError("Unable to get Folder Path", hrT);
                            }

                            ZeroMemory(szUserName, celems(szUserName));

                            ULONG nSize = celems(szUserName);
                            if (GetUserNameEx(NameSamCompatible, szUserName, &nSize) && *szUserName)
                            {
                                pNotifySourceInfo->szUserName = szUserName;
                                fNotifyWZC = TRUE;
                            }
                            else
                            {
                                pNotifySourceInfo->szUserName = L"System";
                                TraceError("Unable to get the user name", HrFromLastWin32Error());
                            }
                        }
                        Lock();
                        m_mapNotify[pUnkSink] = pNotifySourceInfo;
                        Unlock();

#ifdef DBG
                        LPWSTR* ppszAdviseUsers;
                        DWORD   dwCount;
                        HRESULT hrT = GetClientAdvises(&ppszAdviseUsers, &dwCount);
                        if (SUCCEEDED(hrT))
                        {
                            Assert(dwCount);

                            TraceTag(ttidConman, "Advise client list after ::Advise:");
                            for (DWORD x = 0; x < dwCount; x++)
                            {
                                LPWSTR szUserName = ppszAdviseUsers[x];
                                TraceTag(ttidConman, "%x: %S", x, szUserName);
                            }

                            CoTaskMemFree(ppszAdviseUsers);
                        }
#endif
                    }

                    if (SUCCEEDED(hrImpersonate))
                    {
                        CoRevertToSelf();
                    }

                    if (fNotifyWZC)
                    {
                        WZCTrayIconReady(szUserName);
                    }
                }

                if (!m_lRegisteredOneTime)
                {
                    // Do an explicit interlocked exchange to only let one thread
                    // through to do the registration.
                    //
                    if (0 == InterlockedExchange (&m_lRegisteredOneTime, 1))
                    {
                        // Register for device notifications.  Specifically, we're
                        // interested in network adapters coming and going.  If this
                        // fails, we proceed anyway.
                        //
                        TraceTag (ttidConman, "Calling RegisterDeviceNotification...");

                        DEV_BROADCAST_DEVICEINTERFACE PnpFilter;
                        ZeroMemory (&PnpFilter, sizeof(PnpFilter));
                        PnpFilter.dbcc_size         = sizeof(PnpFilter);
                        PnpFilter.dbcc_devicetype   = DBT_DEVTYP_DEVICEINTERFACE;
                        PnpFilter.dbcc_classguid    = GUID_NDIS_LAN_CLASS;

                        m_hDevNotify = RegisterDeviceNotification (
                                            (HANDLE)_Module.m_hStatus,
                                            &PnpFilter,
                                            DEVICE_NOTIFY_SERVICE_HANDLE);
                        if (!m_hDevNotify)
                        {
                            TraceHr (ttidError, FAL, HrFromLastWin32Error(), FALSE,
                                "RegisterDeviceNotification");
                        }

                        (VOID) HrEnsureRegisteredOrDeregisteredWithWmi (TRUE);
                    }
                }
            }
        }
    }

    TraceErrorOptional ("CConnectionManager::Advise", hr, (S_FALSE == hr));
    return hr;
}

STDMETHODIMP
CConnectionManager::Unadvise(DWORD dwCookie)
{
    HRESULT hr = S_OK;
    TraceFileFunc(ttidConman);

    hr = IConnectionPointImpl<CConnectionManager, &IID_INetConnectionNotifySink>::Unadvise(dwCookie);

    Lock();
    BOOL fFound = FALSE;
    for (ITERUSERNOTIFYMAP iter = m_mapNotify.begin(); iter != m_mapNotify.end(); iter++)
    {
        if (iter->second->dwCookie == dwCookie)
        {
            fFound = TRUE;
            delete iter->second;
            m_mapNotify.erase(iter);
            break;
        }
    }
    Unlock();

    if (!fFound)
    {
        TraceTag(ttidError, "Unadvise cannot find advise for cookie 0x%08x in notify map", dwCookie);
    }

#ifdef DBG
    LPWSTR* ppszAdviseUsers;
    DWORD   dwCount;
    HRESULT hrT = GetClientAdvises(&ppszAdviseUsers, &dwCount);
    if (SUCCEEDED(hrT))
    {
        if (!dwCount)
        {
            TraceTag(ttidConman, "Unadvise removed the last advise client");
        }
        else
        {
            TraceTag(ttidConman, "Advise client list after ::Unadvise:");
        }

        for (DWORD x = 0; x < dwCount; x++)
        {
            LPWSTR szUserName = ppszAdviseUsers[x];
            TraceTag(ttidConman, "%x: %S", x, szUserName);
        }

        CoTaskMemFree(ppszAdviseUsers);
    }
#endif
    return hr;
}

#if DBG

//+---------------------------------------------------------------------------
// INetConnectionManagerDebug
//

DWORD
WINAPI
ConmanNotifyTest (
    PVOID   pvContext
    )
{
    HRESULT hr;

    RASENUMENTRYDETAILS*    pDetails;
    DWORD                   cDetails;
    hr = HrRasEnumAllEntriesWithDetails (NULL,
            &pDetails, &cDetails);
    if (SUCCEEDED(hr))
    {
        RASEVENT Event;

        for (DWORD i = 0; i < cDetails; i++)
        {
            Event.Type = ENTRY_ADDED;
            Event.Details = pDetails[i];
            RasEventNotify (&Event);

            Event.Type = ENTRY_MODIFIED;
            RasEventNotify (&Event);

            Event.Type = ENTRY_CONNECTED;
            Event.guidId = pDetails[i].guidId;
            RasEventNotify (&Event);

            Event.Type = ENTRY_DISCONNECTED;
            Event.guidId = pDetails[i].guidId;
            RasEventNotify (&Event);

            Event.Type = ENTRY_RENAMED;
            lstrcpyW (Event.pszwNewName, L"foobar");
            RasEventNotify (&Event);

            Event.Type = ENTRY_DELETED;
            Event.guidId = pDetails[i].guidId;
            RasEventNotify (&Event);
        }

        MemFree (pDetails);
    }

    LanEventNotify (REFRESH_ALL, NULL, NULL, NULL);

    TraceErrorOptional ("ConmanNotifyTest", hr, (S_FALSE == hr));
    return hr;
}

STDMETHODIMP
CConnectionManager::NotifyTestStart ()
{
    HRESULT hr = S_OK;

    if (!QueueUserWorkItem (ConmanNotifyTest, NULL, WT_EXECUTEDEFAULT))
    {
        hr = HrFromLastWin32Error ();
    }

    TraceErrorOptional ("CConnectionManager::NotifyTestStart", hr, (S_FALSE == hr));
    return hr;
}

STDMETHODIMP
CConnectionManager::NotifyTestStop ()
{
    return S_OK;
}

#endif // DBG