//+----------------------------------------------------------------------------
//
//  Windows NT Directory Service Property Pages
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 2001
//
//  File:       misc.cxx, this file is #include'd into the two dllmisc.cxx files.
//
//  Contents:   DS property pages class objects handler DLL fcns. Also, error
//              reporting, message, and miscelaneous functions.
//
//  History:    10-May-01 EricB created
//
//-----------------------------------------------------------------------------

#include "pch.h"
#include "proppage.h"
#include "objlist.h"
#if defined(DSPROP_ADMIN)
#   include "chklist.h"
#   include "fpnw.h"
#endif
#include <time.h>

DECLARE_INFOLEVEL(DsProp);

HINSTANCE g_hInstance = NULL;
ULONG CDll::s_cObjs  = 0;
ULONG CDll::s_cLocks = 0;
UINT g_uChangeMsg = 0;
int g_iInstance = 0;

#ifndef DSPROP_ADMIN
CRITICAL_SECTION g_csNotifyCreate;
#endif

ULONG g_ulMemberFilterCount = DSPROP_MEMBER_FILTER_COUNT_DEFAULT;
ULONG g_ulMemberQueryLimit = DSPROP_MEMBER_QUERY_LIMIT_DEFAULT;

#define DIRECTORY_UI_KEY TEXT("Software\\Policies\\Microsoft\\Windows\\Directory UI")
#define MEMBER_QUERY_VALUE TEXT("GroupMemberFilterCount")
#define MEMBER_LIMIT_VALUE TEXT("GroupMemberQueryLimit")

#if defined(DSPROP_ADMIN)
Cache g_FPNWCache;
#endif

HRESULT GlobalInit(void);
void GlobalUnInit(void);
void ReportErrorFallback(HWND hWndMsg, HRESULT hr = ERROR_SUCCESS)
    { ReportError( hr, 0, hWndMsg ); }

//+----------------------------------------------------------------------------
// To set a non-default debug info level outside of the debugger, create the
// below registry key and in it create a value whose name is the component's
// debugging tag name (the "comp" parameter to the DECLARE_INFOLEVEL macro) and
// whose data is the desired infolevel in REG_DWORD format.
//-----------------------------------------------------------------------------

#define SMDEBUGKEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AdminDebug"

#ifndef DSPROP_ADMIN
//+----------------------------------------------------------------------------
//
//  Class:      CNotifyCreateCriticalSection
//
//  Purpose:    Prevents creation race conditions. Without this protection,
//              a second call to CNotifyObj::Create that comes in before the
//              first has created the hidden window will get NULL back from
//              FindSheetNoSetFocus and then go on to create a second hidden
//              window.
//
//-----------------------------------------------------------------------------
CNotifyCreateCriticalSection::CNotifyCreateCriticalSection()
{
    TRACE(CNotifyCreateCriticalSection, CNotifyCreateCriticalSection);
    EnterCriticalSection(&g_csNotifyCreate);
};

CNotifyCreateCriticalSection::~CNotifyCreateCriticalSection()
{
    TRACE(CNotifyCreateCriticalSection, ~CNotifyCreateCriticalSection);
    LeaveCriticalSection(&g_csNotifyCreate);
};

#endif



#define MAX_STRING 1024

//+----------------------------------------------------------------------------
//
//  Function:   LoadStringToTchar
//
//  Synopsis:   Loads the given string into an allocated buffer that must be
//              caller freed using delete.
//
//-----------------------------------------------------------------------------
BOOL LoadStringToTchar(int ids, PTSTR * pptstr)
{
    TCHAR szBuf[MAX_STRING];

    if (!LoadString(g_hInstance, ids, szBuf, MAX_STRING - 1))
    {
        return FALSE;
    }

    *pptstr = new TCHAR[_tcslen(szBuf) + 1];

    CHECK_NULL(*pptstr, return FALSE);

    _tcscpy(*pptstr, szBuf);

    return TRUE;
}

//+----------------------------------------------------------------------------
// Function: LoadStringReport
// Purpose: attempts to load a string, returns FALSE and gives error message
//          if a failure occurs.
//-----------------------------------------------------------------------------
BOOL LoadStringReport(int ids, PTSTR ptz, int len, HWND hwnd)
{
    if (!LoadString(g_hInstance, ids, ptz, len - 1))
    {
        DWORD dwErr = GetLastError();
        dspDebugOut((DEB_ERROR, "LoadString of %d failed with error %lu\n",
                     ids, dwErr));
        ReportError(dwErr, 0, hwnd);
        return FALSE;
    }
    return TRUE;
}

//+----------------------------------------------------------------------------
//
//  Function:   LoadErrorMessage
//
//  Synopsis:   Attempts to get a user-friendly error message from the system.
//
//-----------------------------------------------------------------------------
void LoadErrorMessage(HRESULT hr, int nStr, PTSTR* pptsz) // free with delete[]
{
    dspAssert( NULL != pptsz && NULL == *pptsz );
    PTSTR ptzFormat = NULL, ptzSysMsg;
    int cch;

    if (nStr)
    {
        LoadStringToTchar(nStr, &ptzFormat);
    }

    cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
                        | FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr,
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                        (PTSTR)&ptzSysMsg, 0, NULL);
    if (!cch)
    {
        // Try ADSI errors.
        //
        cch = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
                            | FORMAT_MESSAGE_FROM_HMODULE,
                            GetModuleHandle(TEXT("activeds.dll")), hr,
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                            (PTSTR)&ptzSysMsg, 0, NULL);
    }

    if (!cch)
    {
        PTSTR ptzMsgTemplate = NULL;
        BOOL fDelMsgTemplate = TRUE;
        LoadStringToTchar(IDS_DEFAULT_ERROR_MSG, &ptzMsgTemplate);
        if (NULL == ptzMsgTemplate)
        {
            ptzMsgTemplate = TEXT("The operation failed with error code %d (0x%08x)");
            fDelMsgTemplate = FALSE;
        }
        *pptsz = new TCHAR[ lstrlen(ptzMsgTemplate)+10 ];
        dspAssert( NULL != *pptsz );
        if (NULL != *pptsz)
        {
            wsprintf(*pptsz, ptzMsgTemplate, hr, hr);
        }
        if (fDelMsgTemplate)
        {
            delete ptzMsgTemplate;
        }
    }
    else
    {
        PTSTR ptzMsg;
        BOOL fDelMsg = FALSE;

        if (ptzFormat)
        {
            ptzMsg = new TCHAR[lstrlen(ptzFormat) + lstrlen(ptzSysMsg) + 1];
            if (ptzMsg)
            {
                wsprintf(ptzMsg, ptzFormat, ptzSysMsg);
                fDelMsg = TRUE;
            }
            else
            {
                ptzMsg = ptzSysMsg;
            }
        }
        else
        {
            ptzMsg = ptzSysMsg;
        }

        *pptsz = new TCHAR[ lstrlen(ptzMsg)+1 ];
        if (NULL != *pptsz)
        {
            lstrcpy( *pptsz, ptzMsg );
        }
        dspAssert( NULL != *pptsz );

        LocalFree(ptzSysMsg);
        if (fDelMsg)
        {
            delete[] ptzMsg;
        }
        if (ptzFormat)
        {
            delete ptzFormat;
        }
    }
}

//+----------------------------------------------------------------------------
//
//  Function:   CheckADsError
//
//  Synopsis:   Checks the result code from an ADSI call.
//
//  Returns:    TRUE if no error.
//
//-----------------------------------------------------------------------------
BOOL CheckADsError(HRESULT * phr, BOOL fIgnoreAttrNotFound, PSTR file,
                   int line, HWND hWnd)
{
    if (SUCCEEDED(*phr))
    {
        return TRUE;
    }

    if (((E_ADS_PROPERTY_NOT_FOUND == *phr) ||
         (HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE) == *phr)) &&
        fIgnoreAttrNotFound)
    {
        *phr = S_OK;
        return TRUE;
    }

    HWND hWndMsg = hWnd;

    if (!hWndMsg)
    {
        hWndMsg = GetDesktopWindow();
    }

    DWORD dwErr;
    WCHAR wszErrBuf[MAX_PATH+1];
    WCHAR wszNameBuf[MAX_PATH+1];
    ADsGetLastError(&dwErr, wszErrBuf, MAX_PATH, wszNameBuf, MAX_PATH);
    if ((LDAP_RETCODE)dwErr == LDAP_NO_SUCH_ATTRIBUTE && fIgnoreAttrNotFound)
    {
        *phr = S_OK;
        return TRUE;
    }
    if (dwErr)
    {
        dspDebugOut((DEB_ERROR,
                     "Extended Error 0x%x: %ws %ws <%s @line %d>.\n", dwErr,
                     wszErrBuf, wszNameBuf, file, line));
        ReportError(dwErr, IDS_ADS_ERROR_FORMAT, hWndMsg);
    }
    else
    {
        dspDebugOut((DEB_ERROR, "Error %08lx <%s @line %d>\n", *phr, file, line));
        ReportError(*phr, IDS_ADS_ERROR_FORMAT, hWndMsg);
    }

    return FALSE;
}

//+----------------------------------------------------------------------------
//
//  Function:   ReportError
//
//  Synopsis:   Displays an error using a user-friendly error message from the
//              system.
//
//-----------------------------------------------------------------------------
void ReportErrorWorker(HWND hWnd, PTSTR ptzMsg)
{
    HWND hWndMsg = hWnd;
    if (!hWndMsg)
    {
        hWndMsg = GetDesktopWindow();
    }

    PTSTR ptzTitle = NULL;
    if (!LoadStringToTchar(IDS_MSG_TITLE, &ptzTitle))
    {
        MessageBox(hWndMsg, ptzMsg, TEXT("Active Directory"), MB_OK | MB_ICONEXCLAMATION);
        return;
    }

    MessageBox(hWndMsg, ptzMsg, ptzTitle, MB_OK | MB_ICONEXCLAMATION);

    delete ptzTitle;
}

void ReportError(HRESULT hr, int nStr, HWND hWnd)
{
    PTSTR ptzMsg = NULL;
    LoadErrorMessage(hr, nStr, &ptzMsg);
    if (NULL == ptzMsg)
    {
        TCHAR tzBuf[80];
        wsprintf(tzBuf, TEXT("Active Directory failure with code '0x%08x'!"), hr);
        ReportErrorWorker( hWnd, tzBuf );
        return;
    }

    ReportErrorWorker( hWnd, ptzMsg );

    delete ptzMsg;
}

#if defined(DSADMIN)
//+----------------------------------------------------------------------------
//
//  Function:   SuperMsgBox
//
//  Synopsis:   Displays a message obtained from a string resource with
//              the parameters expanded. The error param, dwErr, if
//              non-zero, is converted to a string and becomes the first
//              replaceable param.
//
//  Note: this function is UNICODE-only.
//
//-----------------------------------------------------------------------------
int SuperMsgBox(
    HWND hWnd,          // owning window.
    int nMessageId,     // string resource ID of message. Must have replacable params to match nArguments.
    int nTitleId,       // string resource ID of the title. If zero, uses IDS_MSG_TITLE.
    UINT ufStyle,       // MessageBox flags.
    DWORD dwErr,        // Error code, or zero if not needed.
    PVOID * rgpvArgs,   // array of pointers/values for substitution in the nMessageId string.
    int nArguments,     // count of pointers in string array.
    BOOL fTryADSiErrors,// If the failure is the result of an ADSI call, see if an ADSI extended error.
    PSTR szFile,        // use the __FILE__ macro. ignored in retail build.
    int nLine           // use the __LINE__ macro. ignored in retail build.
    )
{
    CStrW strTitle;
    PWSTR pwzMsg = NULL;

    strTitle.LoadString(g_hInstance, (nTitleId) ? nTitleId : IDS_MSG_TITLE);

    if (0 == strTitle.GetLength())
    {
        ReportErrorFallback(hWnd, dwErr);
        return 0;
    }

    DspFormatMessage(nMessageId, dwErr, rgpvArgs, nArguments,
                     fTryADSiErrors, &pwzMsg, hWnd);
    if (!pwzMsg)
    {
       return 0;
    }

    if (dwErr)
    {
        dspDebugOut((DEB_ERROR,
                     "*+*+*+*+* Error <%s @line %d> -> 0x%08x, with message:\n\t%ws\n",
                     szFile, nLine, dwErr, pwzMsg));
    }
    else
    {
        dspDebugOut((DEB_ERROR,
                     "*+*+*+*+* Message <%s @line %d>:\n\t%ws\n",
                     szFile, nLine, pwzMsg));
    }

    int retval = MessageBox(hWnd, pwzMsg, strTitle, ufStyle);

    LocalFree(pwzMsg);
    return retval;
}

//+----------------------------------------------------------------------------
//
//  Function:   DspFormatMessage
//
//  Synopsis:   Loads a string resource with replaceable parameters and uses
//              FormatMessage to populate the replaceable params from the
//              argument array. If dwErr is non-zero, will load the system
//              description for that error and include it in the argument array.
//
//-----------------------------------------------------------------------------
void
DspFormatMessage(
    int nMessageId,     // string resource ID of message. Must have replacable params to match nArguments.
    DWORD dwErr,        // Error code, or zero if not needed.
    PVOID * rgpvArgs,   // array of pointers/values for substitution in the nMessageId string.
    int nArguments,     // count of pointers in string array.
    BOOL fTryADSiErrors,// If the failure is the result of an ADSI call, see if an ADSI extended error.
    PWSTR * ppwzMsg,    // The returned error string, free with LocalFree.
    HWND hWnd           // owning window, defaults to NULL.
    )
{
    int cch;
    PWSTR pwzSysErr = NULL;

    if (dwErr)
    {
        if (fTryADSiErrors)
        {
            DWORD dwStatus;
            WCHAR Buf1[256], Buf2[256];

            ADsGetLastError(&dwStatus, Buf1, 256, Buf2, 256);

            dspDebugOut((DEB_ERROR,
                         "ADsGetLastError returned status of %lx, error: %s, name %s\n",
                          dwStatus, Buf1, Buf2));

            if ((ERROR_INVALID_DATA != dwStatus) && (0 != dwStatus))
            {
                dwErr = dwStatus;
            }
        }

        cch = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                             FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr,
                             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                             (PWSTR)&pwzSysErr, 0, NULL);
        if (!cch)
        {
            ReportErrorFallback(hWnd, dwErr);
            return;
        }
    }

    PWSTR * rgpvArguments = new PWSTR[nArguments + 1];

    if (!rgpvArguments)
    {
        ReportErrorFallback(hWnd, dwErr);
        return;
    }

    int nOffset = 0;

    if (dwErr)
    {
        rgpvArguments[0] = pwzSysErr;
        nOffset = 1;
    }

    if (0 != nArguments)
    {
        CopyMemory(rgpvArguments + nOffset, rgpvArgs, nArguments * sizeof(PVOID));
    }

    CStrW strFormat;

    strFormat.LoadString(g_hInstance, nMessageId);

    if (0 == strFormat.GetLength())
    {
        ReportErrorFallback(hWnd, dwErr);
        return;
    }

    cch = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                         FORMAT_MESSAGE_FROM_STRING |
                         FORMAT_MESSAGE_ARGUMENT_ARRAY, strFormat, 0, 0,
                         (PWSTR)ppwzMsg, 0, (va_list *)rgpvArguments);

    if (pwzSysErr) LocalFree(pwzSysErr);

    if (!cch)
    {
        ReportErrorFallback(hWnd, dwErr);
    }

    delete[] rgpvArguments;
    return;
}

#endif // defined(DSADMIN)

//+----------------------------------------------------------------------------
//
//  Function:   ErrMsg
//
//  Synopsis:   Displays an error message obtained from a string resource.
//
//-----------------------------------------------------------------------------
void ErrMsg(UINT MsgID, HWND hWndParam)
{
    PTSTR ptz = TEXT("");

    ErrMsgParam(MsgID, (LPARAM)ptz, hWndParam);
}

//+----------------------------------------------------------------------------
//
//  Function:   ErrMsgParam
//
//  Synopsis:   Displays an error message obtained from a string resource with
//              an insertion parameter.
//
//  Note: fixed-size stack buffers are used for the strings because trying to
//        report an allocation failure using dynamically allocated string
//        buffers is not a good idea.
//-----------------------------------------------------------------------------
void ErrMsgParam(UINT MsgID, LPARAM param, HWND hWndParam)
{
    HWND hWnd;
    if (hWndParam == NULL)
    {
        hWnd = GetDesktopWindow();
    }
    else
    {
        hWnd = hWndParam;
    }
    TCHAR szTitle[MAX_TITLE+1];
    TCHAR szFormat[MAX_ERRORMSG+1];
    TCHAR szMsg[MAX_ERRORMSG+1];
    LOAD_STRING(IDS_MSG_TITLE, szTitle, MAX_TITLE, MessageBeep(MB_ICONEXCLAMATION); return);
    LOAD_STRING(MsgID, szFormat, MAX_ERRORMSG, MessageBeep(MB_ICONEXCLAMATION); return);
    wsprintf(szMsg, szFormat, param);
    MessageBox(hWnd, szMsg, szTitle, MB_OK | MB_ICONEXCLAMATION);
}

//+----------------------------------------------------------------------------
//
//  Function:   MsgBox
//
//  Synopsis:   Displays a message obtained from a string resource.
//
//-----------------------------------------------------------------------------
void MsgBox(UINT MsgID, HWND hWnd)
{
    MsgBox2(MsgID, 0, hWnd);
}

//+----------------------------------------------------------------------------
//
//  Function:   MsgBox2
//
//  Synopsis:   Displays a message obtained from a string resource which is
//              assumed to have a replacable parameter where the InsertID
//              string is inserted.
//
//-----------------------------------------------------------------------------
void MsgBox2(UINT MsgID, UINT InsertID, HWND hWnd)
{
    CStr strMsg, strFormat, strInsert, strTitle;

    if (!strTitle.LoadString(g_hInstance, IDS_MSG_TITLE))
    {
        REPORT_ERROR(GetLastError(), hWnd);
        return;
    }
    if (!strFormat.LoadString(g_hInstance, MsgID))
    {
        REPORT_ERROR(GetLastError(), hWnd);
        return;
    }
    if (InsertID)
    {
        if (!strInsert.LoadString(g_hInstance, InsertID))
        {
            REPORT_ERROR(GetLastError(), hWnd);
            return;
        }

        strMsg.Format(strFormat, strInsert);
    }
    else
    {
        strMsg = strFormat;
    }

    MessageBox(hWnd, strMsg, strTitle, MB_OK | MB_ICONINFORMATION);
}

//+----------------------------------------------------------------------------
//
//      DLL functions
//
//-----------------------------------------------------------------------------

//+----------------------------------------------------------------------------
//
//  Function:   DllMain
//
//  Synopsis:   Provide a DllMain for Win32
//
//  Arguments:  hInstance - HANDLE to this dll
//              dwReason  - Reason this function was called. Can be
//                          Process/Thread Attach/Detach.
//
//  Returns:    BOOL - TRUE if no error, FALSE otherwise
//
//  History:    24-May-95 EricB created.
//
//-----------------------------------------------------------------------------
extern "C" BOOL
DllMain(HINSTANCE hInstance, DWORD dwReason, PVOID)
{
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            dspDebugOut((DEB_ITRACE, "DllMain: DLL_PROCESS_ATTACH\n"));

            //
            // Get instance handle
            //

            g_hInstance = hInstance;

            //
            // Disable thread notification from OS
            //

            //DisableThreadLibraryCalls(hInstance);

            HRESULT hr;

            //
            // Initialize the global values.
            //

            hr = GlobalInit();
            if (FAILED(hr))
            {
                ERR_OUT("GlobalInit", hr);
                return FALSE;
            }

            break;

        case DLL_PROCESS_DETACH:
            dspDebugOut((DEB_ITRACE, "DllMain: DLL_PROCESS_DETACH\n"));
            GlobalUnInit();
            break;
    }
    return(TRUE);
}

//+----------------------------------------------------------------------------
//
//  Function:   DllGetClassObject
//
//  Synopsis:   Creates a class factory for the requested object.
//
//  Arguments:  [cid]    - the requested class object
//              [iid]    - the requested interface
//              [ppvObj] - returned pointer to class object
//
//  Returns:    HRESULTS
//
//-----------------------------------------------------------------------------
STDAPI
DllGetClassObject(REFCLSID cid, REFIID iid, void **ppvObj)
{
    IUnknown *pUnk = NULL;
    HRESULT hr = S_OK;

    for (int i = 0; i < g_DsPPClasses.cClasses; i++)
    {
        if (cid == *g_DsPPClasses.rgpClass[i]->pcid)
        {
            pUnk = CDsPropPagesHostCF::Create(g_DsPPClasses.rgpClass[i]);
            if (pUnk != NULL)
            {
                hr = pUnk->QueryInterface(iid, ppvObj);
                pUnk->Release();
            }
            else
            {
                return E_OUTOFMEMORY;
            }
            return hr;
        }
    }
    return E_NOINTERFACE;
}

//+----------------------------------------------------------------------------
//
//  Function:   DllCanUnloadNow
//
//  Synopsis:   Indicates whether the DLL can be removed if there are no
//              objects in existence.
//
//  Returns:    S_OK or S_FALSE
//
//-----------------------------------------------------------------------------
STDAPI
DllCanUnloadNow(void)
{
    dspDebugOut((DEB_ITRACE, "DllCanUnloadNow: CDll::CanUnloadNow()? %s\n",
                 (CDll::CanUnloadNow() == S_OK) ? "TRUE" : "FALSE"));
    return CDll::CanUnloadNow();
}

TCHAR const c_szServerType[] = TEXT("InProcServer32");
TCHAR const c_szThreadModel[] = TEXT("ThreadingModel");
TCHAR const c_szThreadModelValue[] = TEXT("Apartment");

//+----------------------------------------------------------------------------
//
//  Function:   DllRegisterServer
//
//  Synopsis:   Adds entries to the system registry.
//
//  Returns:    S_OK or error code.
//
//  Notes:      The keys look like this:
//
//      HKC\CLSID\clsid <No Name> REG_SZ name.progid
//                     \InPropServer32 <No Name> : REG_SZ : adprop.dll/dsprop.dll
//                                     ThreadingModel : REG_SZ : Apartment
//-----------------------------------------------------------------------------
STDAPI
DllRegisterServer(void)
{
    HRESULT hr = S_OK;
    HKEY hKeyCLSID, hKeyDsPPClass, hKeySvr;

    long lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("CLSID"), 0,
                             KEY_WRITE, &hKeyCLSID);
    if (lRet != ERROR_SUCCESS)
    {
        dspDebugOut((DEB_ITRACE, "RegOpenKeyEx failed: %d", lRet));
        return (HRESULT_FROM_WIN32(lRet));
    }

    LPOLESTR pszCLSID;
    PTSTR ptzCLSID;
    DWORD dwDisposition;

    for (int i = 0; i < g_DsPPClasses.cClasses; i++)
    {
        hr = StringFromCLSID(*g_DsPPClasses.rgpClass[i]->pcid, &pszCLSID);

        if (FAILED(hr))
        {
            DBG_OUT("StringFromCLSID failed");
            continue;
        }

        if (!UnicodeToTchar(pszCLSID, &ptzCLSID))
        {
            DBG_OUT("Memory Allocation failure!");
            CoTaskMemFree(pszCLSID);
            hr = E_OUTOFMEMORY;
            continue;
        }

        CoTaskMemFree(pszCLSID);

        lRet = RegCreateKeyEx(hKeyCLSID, ptzCLSID, 0, NULL,
                              REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL,
                              &hKeyDsPPClass, &dwDisposition);

        delete ptzCLSID;

        if (lRet != ERROR_SUCCESS)
        {
            dspDebugOut((DEB_ITRACE, "RegCreateKeyEx failed: %d", lRet));
            hr = HRESULT_FROM_WIN32(lRet);
            continue;
        }

        TCHAR szProgID[MAX_PATH];
        _tcscpy(szProgID, c_szDsProppagesProgID);
        _tcscat(szProgID, g_DsPPClasses.rgpClass[i]->szProgID);

        lRet = RegSetValueEx(hKeyDsPPClass, NULL, 0, REG_SZ,
                             (CONST BYTE *)szProgID,
                             sizeof(TCHAR) * (static_cast<DWORD>(_tcslen(szProgID) + 1)));
        if (lRet != ERROR_SUCCESS)
        {
            RegCloseKey(hKeyDsPPClass);
            dspDebugOut((DEB_ITRACE, "RegSetValueEx failed: %d", lRet));
            hr = HRESULT_FROM_WIN32(lRet);
            continue;
        }

        lRet = RegCreateKeyEx(hKeyDsPPClass, c_szServerType, 0, NULL,
                              REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL,
                              &hKeySvr, &dwDisposition);

        RegCloseKey(hKeyDsPPClass);

        if (lRet != ERROR_SUCCESS)
        {
            dspDebugOut((DEB_ITRACE, "RegCreateKeyEx failed: %d", lRet));
            hr = HRESULT_FROM_WIN32(lRet);
            continue;
        }

        lRet = RegSetValueEx(hKeySvr, NULL, 0, REG_SZ,
                             (CONST BYTE *)c_szDsProppagesDllName,
                         sizeof(TCHAR) * (static_cast<DWORD>(_tcslen(c_szDsProppagesDllName) + 1)));
        if (lRet != ERROR_SUCCESS)
        {
            dspDebugOut((DEB_ITRACE, "RegSetValueEx failed: %d", lRet));
            hr = HRESULT_FROM_WIN32(lRet);
        }

        lRet = RegSetValueEx(hKeySvr, c_szThreadModel, 0, REG_SZ,
                             (CONST BYTE *)c_szThreadModelValue,
                         sizeof(TCHAR) * (static_cast<DWORD>(_tcslen(c_szThreadModelValue) + 1)));
        if (lRet != ERROR_SUCCESS)
        {
            dspDebugOut((DEB_ITRACE, "RegSetValueEx failed: %d", lRet));
            hr = HRESULT_FROM_WIN32(lRet);
        }

        RegCloseKey(hKeySvr);
    }
    RegCloseKey(hKeyCLSID);
    return hr;
}

//+----------------------------------------------------------------------------
//
//  Function:   DllUnregisterServer
//
//  Synopsis:   Removes the entries from the system registry.
//
//  Returns:    S_OK or S_FALSE
//
//  Notes:      The keys look like this:
//
//      HKC\CLSID\clsid <No Name> REG_SZ name.progid
//                     \InPropServer32 <No Name> : REG_SZ : dsprop.dll
//                                     ThreadingModel : REG_SZ : Apartment
//-----------------------------------------------------------------------------
STDAPI
DllUnregisterServer(void)
{
    HRESULT hr = S_OK;
    HKEY hKeyCLSID, hKeyClass;

    long lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("CLSID"), 0,
                             KEY_ALL_ACCESS, &hKeyCLSID);
    if (lRet != ERROR_SUCCESS)
    {
        dspDebugOut((DEB_ITRACE, "RegOpenKeyEx failed: %d", lRet));
        return (HRESULT_FROM_WIN32(lRet));
    }

    LPOLESTR pszCLSID;
    PTSTR ptzCLSID;

    for (int i = 0; i < g_DsPPClasses.cClasses; i++)
    {
        hr = StringFromCLSID(*g_DsPPClasses.rgpClass[i]->pcid, &pszCLSID);

        if (FAILED(hr))
        {
            DBG_OUT("StringFromCLSID failed");
            continue;
        }

        if (!UnicodeToTchar(pszCLSID, &ptzCLSID))
        {
            DBG_OUT("CLSID string memory allocation failure!");
            CoTaskMemFree(pszCLSID);
            hr = E_OUTOFMEMORY;
            continue;
        }

        CoTaskMemFree(pszCLSID);

        lRet = RegOpenKeyEx(hKeyCLSID, ptzCLSID, 0, KEY_ALL_ACCESS, &hKeyClass);

        if (lRet != ERROR_SUCCESS)
        {
            dspDebugOut((DEB_ITRACE, "Failed to open key %S\n", ptzCLSID));
            lRet = ERROR_SUCCESS;
            hr = S_FALSE;
        }
        else
        {
            lRet = RegDeleteKey(hKeyClass, c_szServerType);

            RegCloseKey(hKeyClass);

            dspDebugOut((DEB_ITRACE, "Delete of key %S returned code %d\n",
                         c_szServerType, lRet));

            if (lRet != ERROR_SUCCESS)
            {
                lRet = ERROR_SUCCESS;
                hr = S_FALSE;
            }
        }

        lRet = RegDeleteKey(hKeyCLSID, ptzCLSID);

        dspDebugOut((DEB_ITRACE, "Delete of key %S returned code %d\n",
                     ptzCLSID, lRet));

        delete ptzCLSID;

        if (lRet != ERROR_SUCCESS)
        {
            lRet = ERROR_SUCCESS;
            hr = S_FALSE;
        }
    }
    RegCloseKey(hKeyCLSID);
    return hr;
}

//+----------------------------------------------------------------------------
//
//  Function:   GlobalInit
//
//  Synopsis:   Sets up the global environment.
//
//  Returns:    S_OK or S_FALSE
//
//-----------------------------------------------------------------------------
inline HRESULT
GlobalInit(void)
{
    //
    // Initialize common controls
    //
    InitCommonControls();

    INITCOMMONCONTROLSEX icce;

    icce.dwSize = sizeof(icce);
    icce.dwICC = ICC_DATE_CLASSES;
    InitCommonControlsEx(&icce);

#if defined(DSPROP_ADMIN)
    //
    // Register the "CHECKLIST" control
    //
    RegisterCheckListWndClass();
#endif

#ifndef DSPROP_ADMIN
    //
    // Register the window class for the notification window.
    //
    RegisterNotifyClass();
#endif

    //
    // Register the attribute-changed message.
    //
    g_uChangeMsg = RegisterWindowMessage(DSPROP_ATTRCHANGED_MSG);

    CHECK_NULL(g_uChangeMsg, ;);

    //
    // Major cheesy hack to allow locating windows that are unique to this
    // instance (since hInstance is invariant).
    //
    srand((unsigned)time(NULL));
    g_iInstance = rand();

#ifndef DSPROP_ADMIN
    ExceptionPropagatingInitializeCriticalSection(&g_csNotifyCreate);
#endif

    HRESULT hr = CheckRegisterClipFormats();
    dspAssert( SUCCEEDED(hr) );

    // If found, read the value of the Member class query clause count and
    // number-of-members limit.
    //
    HKEY hKey;
    LONG lRet;
    lRet = RegOpenKeyEx(HKEY_CURRENT_USER, DIRECTORY_UI_KEY, 0, KEY_READ, &hKey);
    if (lRet == ERROR_SUCCESS)
    {
        DWORD dwSize = sizeof(ULONG);
        RegQueryValueEx(hKey, MEMBER_QUERY_VALUE, NULL, NULL,
                        (LPBYTE)&g_ulMemberFilterCount, &dwSize);

        RegQueryValueEx(hKey, MEMBER_LIMIT_VALUE, NULL, NULL,
                        (LPBYTE)&g_ulMemberQueryLimit, &dwSize);
        RegCloseKey(hKey);
    }

    dspDebugOut((DEB_ITRACE | DEB_USER14, "GroupMemberFilterCount set to %d\n", g_ulMemberFilterCount));
    dspDebugOut((DEB_ITRACE | DEB_USER14, "GroupMemberQueryLimit set to %d\n", g_ulMemberQueryLimit));
    return S_OK;
}

//+----------------------------------------------------------------------------
//
//  Function:   GlobalUnInit
//
//  Synopsis:   Do resource freeing.
//
//-----------------------------------------------------------------------------
inline void
GlobalUnInit(void)
{
#if defined(DSPROP_ADMIN)
  if (!g_FPNWCache.empty()) 
  {
    for (Cache::iterator i = g_FPNWCache.begin(); i != g_FPNWCache.end(); i++) 
    {
      if ((*i).second)
        LocalFree((*i).second);
    }
    g_FPNWCache.clear();
  }
#endif
  g_ClassIconCache.ClearAll();

#ifndef DSPROP_ADMIN
  DeleteCriticalSection(&g_csNotifyCreate);
#endif
}

//+----------------------------------------------------------------------------
//
//  Debugging routines.
//
//-----------------------------------------------------------------------------
#if DBG == 1

//////////////////////////////////////////////////////////////////////////////

unsigned long SmAssertLevel = ASSRT_MESSAGE | ASSRT_BREAK | ASSRT_POPUP;
BOOL fInfoLevelInit = FALSE;

//////////////////////////////////////////////////////////////////////////////

static int _cdecl w4dprintf(const char *format, ...);
static int _cdecl w4smprintf(const char *format, va_list arglist);

//////////////////////////////////////////////////////////////////////////////

static int _cdecl w4dprintf(const char *format, ...)
{
   int ret;

   va_list va;
   va_start(va, format);
   ret = w4smprintf(format, va);
   va_end(va);

   return ret;
}


static int _cdecl w4smprintf(const char *format, va_list arglist)
{
   int ret;
   char szMessageBuf[DSP_DEBUG_BUF_SIZE];

   ret = wvnsprintfA(szMessageBuf, DSP_DEBUG_BUF_SIZE - 1, format, arglist);
   OutputDebugStringA(szMessageBuf);
   return ret;
}

//+---------------------------------------------------------------------------
//
//  Function:   _asdprintf
//
//  Synopsis:   Calls smprintf to output a formatted message.
//
//  History:    18-Oct-91   vich Created
//
//----------------------------------------------------------------------------
inline void __cdecl
_asdprintf(
    char const *pszfmt, ...)
{
    va_list va;
    va_start(va, pszfmt);

    smprintf(DEB_FORCE, "Assert", pszfmt, va);

    va_end(va);
}

//+---------------------------------------------------------------------------
//
//  Function:   SmAssertEx, private
//
//  Synopsis:   Display assertion information
//
//  Effects:    Called when an assertion is hit.
//
//----------------------------------------------------------------------------

void APINOT
SmAssertEx(
    char const * szFile,
    int iLine,
    char const * szMessage)
{
    if (SmAssertLevel & ASSRT_MESSAGE)
    {
        DWORD tid = GetCurrentThreadId();

        _asdprintf("%s File: %s Line: %u, thread id %d\n",
            szMessage, szFile, iLine, tid);
    }

    if (SmAssertLevel & ASSRT_POPUP)
    {
        int id = PopUpError(szMessage,iLine,szFile);

        if (id == IDCANCEL)
        {
            DebugBreak();
        }
    }
    else if (SmAssertLevel & ASSRT_BREAK)
    {
        DebugBreak();
    }
}

//+------------------------------------------------------------
// Function:    PopUpError
//
// Synopsis:    Displays a dialog box using provided text,
//              and presents the user with the option to
//              continue or cancel.
//
// Arguments:
//      szMsg --        The string to display in main body of dialog
//      iLine --        Line number of file in error
//      szFile --       Filename of file in error
//
// Returns:
//      IDCANCEL --     User selected the CANCEL button
//      IDOK     --     User selected the OK button
//-------------------------------------------------------------

int APINOT
PopUpError(
    char const *szMsg,
    int iLine,
    char const *szFile)
{
    int id;
    static char szAssertCaption[128];
    static char szModuleName[128];

    DWORD tid = GetCurrentThreadId();
    DWORD pid = GetCurrentProcessId();
    char * pszModuleName;

    if (GetModuleFileNameA(NULL, szModuleName, 128))
    {
        pszModuleName = szModuleName;
    }
    else
    {
        pszModuleName = "Unknown";
    }

    wsprintfA(szAssertCaption,"Process: %s File: %s line %u, thread id %d.%d",
              pszModuleName, szFile, iLine, pid, tid);

    id = MessageBoxA(NULL,
                     szMsg,
                     szAssertCaption,
                     MB_SETFOREGROUND
                     | MB_DEFAULT_DESKTOP_ONLY
                     | MB_TASKMODAL
                     | MB_ICONEXCLAMATION
                     | MB_OKCANCEL);

    //
    // If id == 0, then an error occurred.  There are two possibilities
    // that can cause the error:  Access Denied, which means that this
    // process does not have access to the default desktop, and everything
    // else (usually out of memory).
    //
#ifdef DSPROP_ADMIN
    if (0 == id)
    {
        if (GetLastError() == ERROR_ACCESS_DENIED)
        {
            //
            // Retry this one with the SERVICE_NOTIFICATION flag on.  That
            // should get us to the right desktop.
            //
            id = MessageBoxA(NULL,
                             szMsg,
                             szAssertCaption,
                             MB_SETFOREGROUND
                             | MB_SERVICE_NOTIFICATION
                             | MB_TASKMODAL
                             | MB_ICONEXCLAMATION
                             | MB_OKCANCEL);
        }
    }
#endif
    return id;
}


//+------------------------------------------------------------
// Function:    smprintf
//
// Synopsis:    Prints debug output using a pointer to the
//              variable information. Used primarily by the
//              xxDebugOut macros
//
// Arguements:
//      ulCompMask --   Component level mask used to determine
//                      output ability
//      pszComp    --   String const of component prefix.
//      ppszfmt    --   Pointer to output format and data
//
//-------------------------------------------------------------

void APINOT
smprintf(
    unsigned long ulCompMask,
    char const   *pszComp,
    char const   *ppszfmt,
    va_list       pargs)
{
    if ((ulCompMask & DEB_FORCE) == DEB_FORCE ||
        (ulCompMask | DEF_INFOLEVEL))
    {
        DWORD tid = GetCurrentThreadId();
        DWORD pid = GetCurrentProcessId();
        if ((DEF_INFOLEVEL & (DEB_DBGOUT | DEB_STDOUT)) != DEB_STDOUT)
        {
            if (! (ulCompMask & DEB_NOCOMPNAME))
            {
                w4dprintf("%x.%03x> %s: ", pid, tid, pszComp);
            }

            SYSTEMTIME st;
            GetLocalTime(&st);
            w4dprintf("%02d:%02d:%02d ", st.wHour, st.wMinute, st.wSecond);

            w4smprintf(ppszfmt, pargs);
        }
    }
}

//+----------------------------------------------------------------------------
// Function:    CheckInit
//
// Synopsis:    Performs debugging library initialization
//              including reading the registry for the desired infolevel
//
//-----------------------------------------------------------------------------
void APINOT
CheckInit(char * pInfoLevelString, unsigned long * pulInfoLevel)
{
    HKEY hKey;
    LONG lRet;
    DWORD dwSize;
    if (fInfoLevelInit) return;
    fInfoLevelInit = TRUE;
    lRet = RegOpenKeyExA(HKEY_LOCAL_MACHINE, SMDEBUGKEY, 0, KEY_READ, &hKey);
    if (lRet == ERROR_SUCCESS)
    {
        dwSize = sizeof(unsigned long);
        lRet = RegQueryValueExA(hKey, pInfoLevelString, NULL, NULL,
                                (LPBYTE)pulInfoLevel, &dwSize);
        if (lRet != ERROR_SUCCESS)
        {
            *pulInfoLevel = DEF_INFOLEVEL;
        }
        RegCloseKey(hKey);
    }
}

#endif // DBG == 1

// This wrapper function required to make prefast shut up when we are 
// initializing a critical section in a constructor.

void
ExceptionPropagatingInitializeCriticalSection(LPCRITICAL_SECTION critsec)
{
   __try
   {
      ::InitializeCriticalSection(critsec);
   }

   //
   // propagate the exception to our caller.  
   //
   __except (EXCEPTION_CONTINUE_SEARCH)
   {
   }
}