#include "pch.h"
#pragma hdrstop


/*----------------------------------------------------------------------------
/ Static data for mapping verbs to intersting information
/----------------------------------------------------------------------------*/

// 
// Menu item stored in the DSA to map from external IDs to internal
//

typedef struct
{
    INT    iMenuItem;               // index into menu_items array
} MENUITEM, * LPMENUITEM;

// 
// This table maps classes to verbs that should be added to the menu
// we then add menu item data structures as required.
//

#define MENUCMD_INITITEM      0x0001    // called per menu item
#define MENUCMD_INVOKE        0x0002    // called to invoke the command

//
// Handlers
//

typedef struct
{
    DWORD dwFlags;
    HDPA hdpaSelection;
    LPWSTR pszUserName;
    LPWSTR pszPassword;
} VERBINFO, * LPVERBINFO;
                                                                               
typedef HRESULT (*LPMENUITEMCB)(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags);

HRESULT _UserVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags);
HRESULT _VolumeVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags);
HRESULT _ComputerVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags);
HRESULT _PrinterVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags);

struct
{
    BOOL fNotValidInWAB:1;          // =1 => verb is NOT valid when invoked from WAB
    LPWSTR pObjectClass;            // class name
    UINT uID;                       // name to add for verb   
    UINT idsHelp;                   // help text for this verb
    LPMENUITEMCB pItemCB;           // menu item callback
}   
menu_items[] =
{
    0, L"user",        IDC_USER_OPENHOMEPAGE,      IDS_USER_OPENHOMEPAGE,  _UserVerbCB,
    1, L"user",        IDC_USER_MAILTO,            IDS_USER_MAILTO,        _UserVerbCB,
    0, L"contact",     IDC_USER_OPENHOMEPAGE,      IDS_USER_OPENHOMEPAGE,  _UserVerbCB, 
    1, L"contact",     IDC_USER_MAILTO,            IDS_USER_MAILTO,        _UserVerbCB,
    1, L"group",       IDC_USER_MAILTO,            IDS_USER_MAILTO,        _UserVerbCB, 
    0, L"volume",      IDC_VOLUME_OPEN,            IDS_VOLUME_OPEN,        _VolumeVerbCB,
    0, L"volume",      IDC_VOLUME_EXPLORE,         IDS_VOLUME_EXPLORE,     _VolumeVerbCB,
    0, L"volume",      IDC_VOLUME_FIND,            IDS_VOLUME_FIND,        _VolumeVerbCB, 
    0, L"volume",      IDC_VOLUME_MAPNETDRIVE,     IDS_VOLUME_MAPNETDRIVE, _VolumeVerbCB,
    0, L"computer",    IDC_COMPUTER_MANAGE,        IDS_COMPUTER_MANAGE,    _ComputerVerbCB,
    0, L"printQueue",  IDC_PRINTER_INSTALL,        IDS_PRINTER_INSTALL,    _PrinterVerbCB,
    0, L"printQueue",  IDC_PRINTER_OPEN,           IDS_PRINTER_OPEN,       _PrinterVerbCB,
};

//
// Our class for implementing the standard verbs
// 

class CDsVerbs : public IShellExtInit, IContextMenu
{
private:
    LONG _cRef;
    IDataObject* _pDataObject;
    HDSA _hdsaItems;               // entry per verb on menu
    VERBINFO _vi;

// 
// This public data is used by the verb handlers, they are passed a CDsVerbs*
// as one of their parameters, so using this we then allow them to store what 
// they need in here.
//

public:
    CDsVerbs();
    ~CDsVerbs();

    // IUnknown members
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP QueryInterface( REFIID, LPVOID FAR* );

    // IShellExtInit
    STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID );

    // IContextMenu
    STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags);
    STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pCMI);
    STDMETHODIMP GetCommandString(UINT_PTR uID, UINT uFlags, UINT FAR* reserved, LPSTR pName, UINT ccMax);

private:
    VOID FreeMenuStateData(VOID);
};


/*----------------------------------------------------------------------------
/ CDsVerbs implementation
/----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
/ IUnknown
/----------------------------------------------------------------------------*/

CDsVerbs::CDsVerbs() :
    _cRef(1),
    _pDataObject(NULL),
    _hdsaItems(NULL)
{
    _vi.dwFlags = 0;
    _vi.hdpaSelection = NULL;
    _vi.pszUserName = NULL;
    _vi.pszPassword = NULL;

    DllAddRef();
}

CDsVerbs::~CDsVerbs()
{
    DoRelease(_pDataObject);

    FreeMenuStateData();

    LocalFreeStringW(&_vi.pszUserName);
    LocalFreeStringW(&_vi.pszPassword);

    DllRelease();
}


// IUnknown bits

ULONG CDsVerbs::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

ULONG CDsVerbs::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

HRESULT CDsVerbs::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CDsVerbs, IShellExtInit),      // IID_IShellExtInit
        QITABENT(CDsVerbs, IContextMenu),       // IID_IContextMenu
        {0, 0 },
    };
    return QISearch(this, qit, riid, ppv);
}


// handle create instance

STDAPI CDsVerbs_CreateInstance(IUnknown* punkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    CDsVerbs *pdv = new CDsVerbs();
    if ( !pdv )
        return E_OUTOFMEMORY;

    HRESULT hres = pdv->QueryInterface(IID_IUnknown, (void **)ppunk);
    pdv->Release();
    return hres;
}


/*----------------------------------------------------------------------------
/ IShellExtInit
/----------------------------------------------------------------------------*/

STDMETHODIMP CDsVerbs::Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObject, HKEY hKeyID)
{
    HRESULT hr;

    TraceEnter(TRACE_VERBS, "CDsVerbs::Initialize");

    // take a copy of the IDataObject if we are given one

    if ( !pDataObject )
        ExitGracefully(hr, E_FAIL, "No IDataObject to interact with");

    DoRelease(_pDataObject);

    _pDataObject = pDataObject;
    _pDataObject->AddRef();

    hr = S_OK;                          // sucess

exit_gracefully:

    TraceLeaveResult(hr);
}


/*----------------------------------------------------------------------------
/ IContextMenu
/----------------------------------------------------------------------------*/

STDMETHODIMP CDsVerbs::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
    HRESULT hr;
    FORMATETC fmte = {(CLIPFORMAT)0, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    STGMEDIUM mediumDsObjects = { TYMED_NULL };
    STGMEDIUM mediumDispSpecOptions = { TYMED_NULL };
    LPDSOBJECTNAMES pDsObjectNames;
    LPDSDISPLAYSPECOPTIONS pDispSpecOptions;
    MENUITEM item;
    INT i, iVerb;
    TCHAR szBuffer[MAX_PATH];
    BOOL fInWAB = FALSE;
    USES_CONVERSION;

    TraceEnter(TRACE_VERBS, "CDsVerbs::QueryContextMenu");

    FreeMenuStateData();

    // Get the selection from the IDataObject we have been given.  This structure
    // contains the object class, ADsPath and other information.

    if ( !_pDataObject )
        ExitGracefully(hr, E_FAIL, "No IDataObject to use");    

    fmte.cfFormat = g_cfDsObjectNames;    
    hr = _pDataObject->GetData(&fmte, &mediumDsObjects);
    FailGracefully(hr, "Failed to get the DSOBJECTNAMES from IDataObject");

    pDsObjectNames = (LPDSOBJECTNAMES)mediumDsObjects.hGlobal;
    TraceAssert(pDsObjectNames);

    fmte.cfFormat = g_cfDsDispSpecOptions;    
    if ( SUCCEEDED(_pDataObject->GetData(&fmte, &mediumDispSpecOptions)) )
    {
        pDispSpecOptions = (LPDSDISPLAYSPECOPTIONS)mediumDispSpecOptions.hGlobal;
        TraceAssert(pDispSpecOptions);

        TraceMsg("Retrieved the CF_DISPSPECOPTIONS from the IDataObject");

        fInWAB = (pDispSpecOptions->dwFlags & DSDSOF_INVOKEDFROMWAB) == DSDSOF_INVOKEDFROMWAB;
        Trace(TEXT("Invoked from WAB == %d"), fInWAB);

        // copy credential and other information for the verbs to invoke with

        _vi.dwFlags = pDispSpecOptions->dwFlags;

        if ( _vi.dwFlags & DSDSOF_HASUSERANDSERVERINFO )
        {
            TraceMsg("Copying user and credential information from clipboard block");

            if ( pDispSpecOptions->offsetUserName )
            {
                LPWSTR pszUserName = (LPWSTR)ByteOffset(pDispSpecOptions, pDispSpecOptions->offsetUserName);
                Trace(TEXT("pszUserName: %s"), W2T(pszUserName));

                hr = LocalAllocStringW(&_vi.pszUserName, pszUserName);
                FailGracefully(hr, "Failed to copy the user name");
            }

            if ( pDispSpecOptions->offsetPassword )
            {
                LPWSTR pszPassword = (LPWSTR)ByteOffset(pDispSpecOptions, pDispSpecOptions->offsetPassword);
                Trace(TEXT("pszPassword: %s"), W2T(pszPassword));

                hr = LocalAllocStringW(&_vi.pszPassword, pszPassword);
                FailGracefully(hr, "Failed to copy the password");
            }
        }
    }

    // Take the first item of the selection, compare all the objects in the
    // rest of the DSOBJECTNAMES, all those who have the same class.

    _hdsaItems = DSA_Create(SIZEOF(MENUITEM), 4);
    TraceAssert(_hdsaItems);

    _vi.hdpaSelection = DPA_Create(4);
    TraceAssert(_vi.hdpaSelection);

    if ( !_vi.hdpaSelection || !_hdsaItems )
        ExitGracefully(hr, E_OUTOFMEMORY, "Failed to allocate the selection DPA");
        
    for ( i = 0 ; i < (INT)pDsObjectNames->cItems ; i++ )
    {
        LPCWSTR pObjectClass0 = (LPCWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[0].offsetClass);
        LPWSTR pPath = (LPWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[i].offsetName);
        LPCWSTR pObjectClass = (LPCWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[i].offsetClass);

        Trace(TEXT("ADsPath of object %d is %s"), i, W2CT(pPath));
        Trace(TEXT("objectClass of object %d is %s"), i, W2CT(pObjectClass));

        if ( !StrCmpW(pObjectClass0, pObjectClass) )
        {
            Trace(TEXT("Adding item %d to the selection DPA"), i);

            hr = StringDPA_AppendStringW(_vi.hdpaSelection, pPath, NULL);
            FailGracefully(hr, "Failed to copy selection to selection DPA");
        }
    }

    // Walk the list of menu items, lets see which ones we need to add to the
    // menu.

    if ( DPA_GetPtrCount(_vi.hdpaSelection) )
    {
        LPCWSTR pObjectClass0 = (LPCWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[0].offsetClass);

        for ( i = 0 ; i < ARRAYSIZE(menu_items); i++ )
        {
            if ( menu_items[i].fNotValidInWAB && fInWAB )
            {
                TraceMsg("Skipping verb not valid for WAB");
                continue;
            }

            if ( !StrCmpW(pObjectClass0, menu_items[i].pObjectClass ) )  
            {
                Trace(TEXT("Adding the verb at index %d to the menu"), i);

                // now fill in the MENUITEM structure and add it to the DSA list,
                // then add the menu item itself, calling the callback so it can
                // enable/disable itself.

                item.iMenuItem = i;

                iVerb = DSA_AppendItem(_hdsaItems, &item);
                TraceAssert(iVerb != -1);

                if ( iVerb != -1 )
                {
                    Trace(TEXT("iVerb is %d"), iVerb);
    
                    LoadString(GLOBAL_HINSTANCE, menu_items[i].uID, szBuffer, ARRAYSIZE(szBuffer));
                    InsertMenu(hMenu, iVerb+indexMenu, MF_BYPOSITION|MF_STRING, iVerb+idCmdFirst, szBuffer);

                    menu_items[i].pItemCB(MENUCMD_INITITEM,
                                          NULL,
                                          hMenu, 
                                          MAKELPARAM(menu_items[i].uID, iVerb+idCmdFirst),
                                          &_vi,
                                          uFlags);
                }
            } 
        }
    }
   
    hr = S_OK;

exit_gracefully:

    if ( SUCCEEDED(hr) )
    {
        Trace(TEXT("%d items added by QueryContextMenu"), DSA_GetItemCount(_hdsaItems));
        hr = ResultFromShort(DSA_GetItemCount(_hdsaItems));
    }

    ReleaseStgMedium(&mediumDsObjects);
    ReleaseStgMedium(&mediumDispSpecOptions);

    TraceLeaveResult(hr);
}

/*---------------------------------------------------------------------------*/

STDMETHODIMP CDsVerbs::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
{
    HRESULT hr;
    UINT uID = LOWORD(lpcmi->lpVerb);
    LPMENUITEM pMenuItem;

    TraceEnter(TRACE_VERBS, "CDsVerbs::InvokeCommand");

    // Dreference the menu item to get the index's into both the item list and the
    // menu table.  With both of these we can then invoke the command.

    Trace(TEXT("uID %d (DSA contains %d)"), uID, DSA_GetItemCount(_hdsaItems));

    if ( !_hdsaItems )
        ExitGracefully(hr, E_UNEXPECTED, "No _hdasItems");

    pMenuItem = (LPMENUITEM)DSA_GetItemPtr(_hdsaItems, (UINT)uID);
    TraceAssert(pMenuItem);

    if ( !pMenuItem || !menu_items[pMenuItem->iMenuItem].pItemCB )
        ExitGracefully(hr, E_UNEXPECTED, "Failed because pItem == NULL");

    hr = menu_items[pMenuItem->iMenuItem].pItemCB(MENUCMD_INVOKE,
                                                  lpcmi->hwnd,
                                                  NULL, 
                                                  MAKELPARAM(menu_items[pMenuItem->iMenuItem].uID, 0),
                                                  &_vi,
                                                  0);
exit_gracefully:

    TraceLeaveResult(S_OK);
}

/*---------------------------------------------------------------------------*/

STDMETHODIMP CDsVerbs::GetCommandString(UINT_PTR uID, UINT uFlags, UINT FAR* reserved, LPSTR pszName, UINT ccMax)
{
    HRESULT hr = E_NOTIMPL;
    INT cc;

    TraceEnter(TRACE_VERBS, "CDsVerbs::GetCommandString");

    if ( _hdsaItems )
    {
        LPMENUITEM pMenuItem = (LPMENUITEM)DSA_GetItemPtr(_hdsaItems, (INT)uID);
        TraceAssert(pMenuItem);

        if ( !pMenuItem )
            ExitGracefully(hr, E_FAIL, "Failed to get menu item");

        if ( uFlags == GCS_HELPTEXT )
        {
            // Get the menu item and look up the resource for this verb
            // and return it into the callers buffer.
            
            if ( !LoadString(GLOBAL_HINSTANCE, menu_items[pMenuItem->iMenuItem].idsHelp, (LPTSTR)pszName, ccMax) ) 
                ExitGracefully(hr, E_FAIL, "Failed to load string for help text");
        }
        else
        {
            ExitGracefully(hr, E_FAIL, "Failed to get command string");
        }
    }

    hr = S_OK;

exit_gracefully:

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ CDsVerbs::FreeMenuStateData
/ ---------------------------
/   Release the verb state data for the CDsVerbs class, this can be called
/   (and is) during the destructor and during the context menu construction
/   to ensure a consistent state.
/
/ In:
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

VOID CDsVerbs::FreeMenuStateData(VOID)
{
    TraceEnter(TRACE_VERBS, "CDsVerbs::FreeMenuStateData");

    if ( _hdsaItems )
    {
        DSA_Destroy(_hdsaItems);
        _hdsaItems = NULL;
    }

    StringDPA_Destroy(&_vi.hdpaSelection);
    LocalFreeStringW(&_vi.pszUserName);
    LocalFreeStringW(&_vi.pszPassword);

    TraceLeave();
}


/*----------------------------------------------------------------------------
/ User object verbs
/----------------------------------------------------------------------------*/

VOID _BuildMailToLine(LPTSTR pBuffer, UINT* pLen, HDPA hdpaMailTo)
{
    INT i;
    
    TraceEnter(TRACE_VERBS, "_BuildMailToLine");

    *pLen = 0;
    PutStringElement(pBuffer, pLen, TEXT("mailto:"));

    for ( i = 0 ; i < DPA_GetPtrCount(hdpaMailTo); i++ )
    {
        LPTSTR pMailTo = (LPTSTR)DPA_GetPtr(hdpaMailTo, i);
        TraceAssert(pMailTo);

        Trace(TEXT("Sending mail to:"), pMailTo);

        if ( i )
        {
            TraceMsg("Adding sepereator");
            PutStringElement(pBuffer, pLen, TEXT(";"));
        }

        PutStringElement(pBuffer, pLen, pMailTo);
    }

    TraceLeave();
}

HRESULT _UserVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags)
{
    HRESULT hr;
    HDPA hdpaMailTo = NULL;
    LPTSTR pURL = NULL;
    LPTSTR pMailTo = NULL;
    UINT cchMailTo = 0;
    IADs* pDsObject = NULL;
    VARIANT variant;
    SHELLEXECUTEINFO sei = { 0 };
    INT i;
    USES_CONVERSION;
    DECLAREWAITCURSOR = GetCursor();

    TraceEnter(TRACE_VERBS, "_UserVerbCB");

    VariantInit(&variant);

    switch ( uCmd )
    {
        case MENUCMD_INITITEM:
        {
            // if this is a map network drive/find volume verb then lets ensure we only handle
            // a single selection.

            switch ( LOWORD(uID) )
            {
                case IDC_USER_OPENHOMEPAGE:
                {
                    if ( DPA_GetPtrCount(pvi->hdpaSelection) != 1 )
                    {
                        TraceMsg("Disabling as selection > 1");
                        EnableMenuItem(hMenu, HIWORD(uID), MF_BYCOMMAND|MF_GRAYED);
                    }

                    break;
                }
            }

            break;
        }

        case MENUCMD_INVOKE:
        {
            // if we have a selection and the user has picked a verb then we
            // need to get the UNC"s from the objects we are trying to invoke,
            // therefore lets build a DPA containing them.

            SetWaitCursor();

            for ( i = 0 ; i < DPA_GetPtrCount(pvi->hdpaSelection); i++ )
            {
                LPWSTR pPath = (LPWSTR)DPA_GetPtr(pvi->hdpaSelection, i);
                TraceAssert(pPath);

                DoRelease(pDsObject);
                VariantClear(&variant);

                Trace(TEXT("Binding to %s"), W2T(pPath));

                if ( FAILED(ADsOpenObject(pPath, pvi->pszUserName, pvi->pszPassword, 
                                          (pvi->dwFlags & DSDSOF_SIMPLEAUTHENTICATE) ? 0:ADS_SECURE_AUTHENTICATION, 
                                          IID_IADs, (LPVOID*)&pDsObject)) )
                {
                    TraceMsg("Failed to bind to the object");
                    continue;
                }

                if ( LOWORD(uID) == IDC_USER_OPENHOMEPAGE ) 
                {
                    // get the web address of the object and store it, this should
                    // only happen once.                 

                    if ( FAILED(pDsObject->Get(L"wWWHomePage", &variant)) )
                        continue;

                    if ( V_VT(&variant) == VT_BSTR )
                    {
                        Trace(TEXT("Storing URL "), W2T(V_BSTR(&variant)));

                        hr = LocalAllocStringW2T(&pURL, V_BSTR(&variant));
                        FailGracefully(hr, "Failed to store the URL");
                    }                   
                }
                else
                {
                    // ensure we have a DPA for storing the mail addresses of the
                    // objects we are invoked on.

                    if ( !hdpaMailTo )
                    {
                        hdpaMailTo = DPA_Create(4);
                        TraceAssert(hdpaMailTo);

                        if ( !hdpaMailTo )
                            ExitGracefully(hr, E_OUTOFMEMORY, "Failed to create the DPA for mail addresses");
                    }

                    if ( FAILED(pDsObject->Get(L"mail", &variant)) )
                        continue;

                    if ( V_VT(&variant) == VT_BSTR )
                    {
                        Trace(TEXT("Adding mail address %s to DPA"), W2T(V_BSTR(&variant)));
                        StringDPA_AppendString(hdpaMailTo, W2T(V_BSTR(&variant)), NULL);
                    }
                }
            }

            // now process the argument list that we have built.

            ResetWaitCursor();

            sei.cbSize = SIZEOF(sei);
            sei.hwnd = hWnd;
            sei.nShow = SW_SHOWNORMAL;

            switch ( LOWORD(uID) )
            {
                case IDC_USER_OPENHOMEPAGE:
                {
                    // if we have a URL then lets pass it to shell execute,
                    // otherwise report the failure to the user.

                    if ( !pURL )
                    {
                        FormatMsgBox(hWnd, GLOBAL_HINSTANCE, IDS_TITLE, IDS_ERR_NOHOMEPAGE, MB_OK|MB_ICONERROR);
                        ExitGracefully(hr, E_FAIL, "No URL defined");
                    }
 
                    Trace(TEXT("Executing URL %s"), pURL);
                    sei.lpFile = pURL;

                    if ( !ShellExecuteEx(&sei) )
                        ExitGracefully(hr, E_UNEXPECTED, "Failed in ShellExecuteEx");

                    break;
                }

                case IDC_USER_MAILTO:
                {
                    // If every single bind operation failed above,
                    // hdpaMailTo didn't get defined, and we'll fault
                    // if we try to use it.
                    if (hdpaMailTo)
                    {
                        // build a command line we can use for the mail to verb.

                        if ( DPA_GetPtrCount(hdpaMailTo) <= 0 )
                        {
                            FormatMsgBox(hWnd, GLOBAL_HINSTANCE, IDS_TITLE, IDS_ERR_NOMAILADDR, MB_OK|MB_ICONERROR);
                            ExitGracefully(hr, E_FAIL, "No mail addresses defined");
                        }

                        _BuildMailToLine(NULL, &cchMailTo, hdpaMailTo);

                        hr = LocalAllocStringLen(&pMailTo, cchMailTo);
                        FailGracefully(hr, "Failed to allocate mailto line:");

                        _BuildMailToLine(pMailTo, &cchMailTo, hdpaMailTo);
                
                        Trace(TEXT("Executing: %s"), pMailTo);
                        sei.lpFile = pMailTo;

                        if ( !ShellExecuteEx(&sei) )
                            ExitGracefully(hr, E_UNEXPECTED, "Failed in ShellExecuteEx");

                    }
                    else
                    {
                        //FEATURE:  We need an error message, here
//                        FormatMsgBox(hWnd, GLOBAL_HINSTANCE, IDS_TITLE, IDS_ERR_NOMAILADDR, MB_OK|MB_ICONERROR);
                        ExitGracefully(hr, E_FAIL, "hdpaMailTo never initialized!");
                    }
                    break;
                }
            }
        }
    }

    hr = S_OK;                  // success

exit_gracefully:

    DoRelease(pDsObject);
    VariantClear(&variant);

    LocalFreeString(&pURL);

    StringDPA_Destroy(&hdpaMailTo);
    LocalFreeString(&pMailTo);

    ResetWaitCursor();

    TraceLeaveResult(hr);
}


/*----------------------------------------------------------------------------
/ Volume object verbs
/----------------------------------------------------------------------------*/

HRESULT _VolumeVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags)
{
    HRESULT hr;
    HDPA hdpaUNC = NULL;
    IADs* pDsObject = NULL;
    VARIANT variant;
    INT i;
    LPITEMIDLIST pidl;
    USES_CONVERSION;
    DECLAREWAITCURSOR = GetCursor();

    TraceEnter(TRACE_VERBS, "_VolumeVerbCB");

    VariantInit(&variant);

    switch ( uCmd )
    {
        case MENUCMD_INITITEM:
        {
            // if this is a map network drive/find volume verb then lets ensure we only handle
            // a single selection.

            switch ( LOWORD(uID) )
            {
                case IDC_VOLUME_FIND:
                case IDC_VOLUME_MAPNETDRIVE:
                {
                    if ( DPA_GetPtrCount(pvi->hdpaSelection) != 1 )
                    {
                        TraceMsg("Disabling as selection > 1");
                        EnableMenuItem(hMenu, HIWORD(uID), MF_BYCOMMAND|MF_GRAYED);
                    }

                    // we remove the find verb if we the restrictions apply to remove it.

                    if ( LOWORD(uID) == IDC_VOLUME_FIND )
                    {
                        if ( SHRestricted(REST_NOFIND) )
                        {
                            TraceMsg("Restriction says 'no find', so deleting the find verb");
                            DeleteMenu(hMenu, HIWORD(uID), MF_BYCOMMAND);
                        }
                    }

                    break;
                }

                case IDC_VOLUME_OPEN:
                {
                    if ( !(uFlags & CMF_EXPLORE) )
                    {
                        TraceMsg("Not exploring, so making open the default verb");
                        SetMenuDefaultItem(hMenu, HIWORD(uID), MF_BYCOMMAND);
                    }

                    break;
                }

                case IDC_VOLUME_EXPLORE:
                {
                    if ( uFlags & CMF_EXPLORE )
                    {
                        TraceMsg("Exploring so making explore the default verb");
                        SetMenuDefaultItem(hMenu, HIWORD(uID), MF_BYCOMMAND);
                    }

                    break;
                }
            }

            break;
        }

        case MENUCMD_INVOKE:
        {
            // if we have a selection and the user has picked a verb then we
            // need to get the UNC"s from the objects we are trying to invoke,
            // therefore lets build a DPA containing them.

            SetWaitCursor();

            hdpaUNC = DPA_Create(4);
            TraceAssert(hdpaUNC);

            if ( !hdpaUNC )
                ExitGracefully(hr, E_OUTOFMEMORY, "Failed to get UNC DPA");

            for ( i = 0 ; i < DPA_GetPtrCount(pvi->hdpaSelection); i++ )
            {
                LPWSTR pPath = (LPWSTR)DPA_GetPtr(pvi->hdpaSelection, i);
                TraceAssert(pPath);

                DoRelease(pDsObject);
                VariantClear(&variant);

                Trace(TEXT("Binding to %s"), W2T(pPath));

                if ( FAILED(ADsOpenObject(pPath, pvi->pszUserName, pvi->pszPassword, 
                                          (pvi->dwFlags & DSDSOF_SIMPLEAUTHENTICATE) ? 0:ADS_SECURE_AUTHENTICATION, 
                                          IID_IADs, (LPVOID*)&pDsObject)) )        
                {
                    TraceMsg("Failed to bind to the object");
                    continue;
                }

                if ( FAILED(pDsObject->Get(L"uNCName", &variant)) )
                    continue;
                
                if ( V_VT(&variant) == VT_BSTR )
                {
                    Trace(TEXT("Adding UNC %s to DPA"), W2T(V_BSTR(&variant)));
                    StringDPA_AppendString(hdpaUNC, W2T(V_BSTR(&variant)), NULL);
                }
            }

            ResetWaitCursor();

            // we now have the selection stored in the DPA, so lets invoke the command
            // by walking the list of UNC's and calling the relevant invoke logic.

            Trace(TEXT("UNC DPA contains %d entries"), DPA_GetPtrCount(hdpaUNC));

            if ( !DPA_GetPtrCount(hdpaUNC) )
            {
                FormatMsgBox(hWnd, GLOBAL_HINSTANCE, IDS_TITLE, IDS_ERR_NOUNC, MB_OK|MB_ICONERROR);
                ExitGracefully(hr, E_FAIL, "No UNC paths defined");
            }

            for ( i = 0 ; i < DPA_GetPtrCount(hdpaUNC); i++ )
            {
                LPTSTR pUNC = (LPTSTR)DPA_GetPtr(hdpaUNC, i);
                TraceAssert(pUNC);

                Trace(TEXT("pUNC is %s"), pUNC);

                switch ( LOWORD(uID) )
                {
                    // explore and open we pass onto the shell.

                    case IDC_VOLUME_OPEN:
                    case IDC_VOLUME_EXPLORE:
                    {
                        SHELLEXECUTEINFO sei = { 0 };       // clears the structure

                        TraceMsg("Trying to open/explore to UNC");

                        sei.cbSize = SIZEOF(sei);
                        sei.hwnd = hWnd;
                        sei.lpFile = pUNC;
                        sei.nShow = SW_SHOWNORMAL;

                        if ( uID == IDC_VOLUME_EXPLORE )
                            sei.lpVerb = TEXT("explore");

                        ShellExecuteEx(&sei);
                        break;
                    }

                    // find we show the find UI by building an ITEMIDLIST for the UNC we
                    // have and then call the shells find UI.

                    case IDC_VOLUME_FIND:
                    {
                        TraceMsg("Invoking find on the UNC");

                        if ( SUCCEEDED(SHILCreateFromPath(pUNC, &pidl, NULL)) )
                        {
                            SHFindFiles(pidl, NULL);
                            ILFree(pidl);
                        }

                        break;
                    }

                    // lets get a net connection from SHStartNetConnection...

                    case IDC_VOLUME_MAPNETDRIVE:
                    {
                        Trace(TEXT("Invoking Map Network Drive for: %s"), pUNC);
                        SHStartNetConnectionDialog(hWnd, pUNC, RESOURCETYPE_DISK);
                        break;
                    }

                    default:
                    {
                        TraceAssert(FALSE);
                        ExitGracefully(hr, E_UNEXPECTED, "Failed to invoke, bad uID");
                    }
                }
            }
        }
    }

    hr = S_OK;                  // success

exit_gracefully:

    DoRelease(pDsObject);
    VariantClear(&variant);

    StringDPA_Destroy(&hdpaUNC);

    ResetWaitCursor();

    TraceLeaveResult(hr);
}


/*----------------------------------------------------------------------------
/ Computer object verbs
/----------------------------------------------------------------------------*/

HRESULT _ComputerVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags)
{
    HRESULT hr;
    INT i;
    IADs * pDsObject = NULL;
    LPTSTR pArguments = NULL;
    LPTSTR pComputer = NULL;
    TCHAR szBuffer[MAX_PATH];
    USES_CONVERSION;
    DECLAREWAITCURSOR = GetCursor();

    TraceEnter(TRACE_VERBS, "_ComputerVerbCB");

    if ( LOWORD(uID) != IDC_COMPUTER_MANAGE )
        ExitGracefully(hr, E_INVALIDARG, "Not computer manange, so bailing");

    switch ( uCmd )
    {
        case MENUCMD_INITITEM:
        {
            if ( DPA_GetPtrCount(pvi->hdpaSelection) != 1 )
            {
                TraceMsg("Selection is != 1, so disabling verb");
                EnableMenuItem(hMenu, HIWORD(uID), MF_BYCOMMAND|MF_GRAYED);
            }

            break;
        }

        case MENUCMD_INVOKE:
        {
            LPWSTR pPath = (LPWSTR)DPA_GetPtr(pvi->hdpaSelection, 0);       // selection always 0
            TraceAssert(pPath);

            hr = ADsOpenObject(pPath, pvi->pszUserName, pvi->pszPassword, 
                              (pvi->dwFlags & DSDSOF_SIMPLEAUTHENTICATE) ? 0:ADS_SECURE_AUTHENTICATION, 
                              IID_IADs, (LPVOID*)&pDsObject);

            FailGracefully(hr, "Failed to bind to computer object");
    
            VARIANT vNetAddr;
            hr = pDsObject->Get(L"dNSHostName", &vNetAddr);
            
            if (SUCCEEDED(hr)) {
              hr = LocalAllocString (&pComputer, W2T(vNetAddr.bstrVal));
              FailGracefully(hr, "Failed to copy computer address somewhere interesting");
            } else {
              if (hr == E_ADS_PROPERTY_NOT_FOUND) {
                hr = pDsObject->Get(L"sAMAccountName", &vNetAddr);
                if (SUCCEEDED(hr)) {
                  hr = LocalAllocString(&pComputer, W2T (vNetAddr.bstrVal));
                  FailGracefully(hr, "Failed to copy SAM account name somewhere interesting");
                  
                  // To make the computer name useful we must remove the trailing dollar if
                  // there is one.  Therefore scan to the end of the string and nuke the
                  // last character.
                  
                  INT i = lstrlen(pComputer);
                  TraceAssert(i > 1);
                  
                  if ( (i > 1) && (pComputer[i-1] == TEXT('$')) )
                    {
                      pComputer[i-1] = TEXT('\0');
                      Trace(TEXT("Fixed computer name: %s"), pComputer);
                    }
                  
                } else 
                  FailGracefully (hr, "Failed to find a usable machine address");
              }
            }
            hr = FormatMsgResource(&pArguments, g_hInstance, IDS_COMPUTER_MANAGECMD, pComputer);
            FailGracefully(hr, "Failed to format MMC cmd line");
            
            ExpandEnvironmentStrings(pArguments, szBuffer, ARRAYSIZE(szBuffer));
            Trace(TEXT("MMC cmd line: mmc.exe %s"), szBuffer);

            ResetWaitCursor();

            ShellExecute(NULL, NULL, TEXT("mmc.exe"), szBuffer,  NULL, SW_SHOWNORMAL);
        }
    }

    hr = S_OK;                  // success

exit_gracefully:

    DoRelease(pDsObject);

    LocalFreeString (&pComputer);

    TraceLeaveResult(hr);
}


/*----------------------------------------------------------------------------
/ printQueue object verb implementations
/----------------------------------------------------------------------------*/

//
// Windows 2000 (and beyond) use a RunDll32 entry point.
//

#define PRINT_FMT            TEXT("printui.dll,PrintUIEntry /n \"%s\" ")
#define PRINT_SWITCH_OPEN    TEXT("/o ")
#define PRINT_SWITCH_INSTALL TEXT("/in ")

BOOL _PrinterCheckRestrictions(HWND hwnd, RESTRICTIONS rest)
{
    if ( SHRestricted(rest) )
    {
        FormatMsgBox(hwnd, GLOBAL_HINSTANCE, IDS_RESTRICTIONSTITLE, IDS_RESTRICTIONS, MB_OK|MB_ICONERROR);
        return TRUE;
    }
    return FALSE;
}

HRESULT _PrinterRunDLLCountAtSymbols( LPCTSTR pszPrinterName, UINT *puCount )
{
    HRESULT hr = E_FAIL;

    if( pszPrinterName && puCount )
    {
        *puCount = 0;

        while( *pszPrinterName )
        {
            if( TEXT('@') == *pszPrinterName++ )
            {
                (*puCount) ++;
            }
        }

        hr = S_OK;
    }

    return hr;
}

HRESULT _PrinterRunDLLFormatAtSymbols( LPTSTR pszBuffer, UINT uBufSize, LPCTSTR pszPrinterName )
{
    HRESULT hr = E_FAIL;

    if( pszPrinterName && pszBuffer && uBufSize )
    {
        // the buffer end - where we will put the zero terminator
        LPTSTR  pszBufEnd = pszBuffer + uBufSize - 1;

        // format the printer name quoting the @ symbols
        while( *pszPrinterName )
        {
            if( TEXT('@') == *pszPrinterName )
            {
                // check the buffer size
                if( (pszBuffer+1) >= pszBufEnd )
                    break; // not enough space

                // we have space in the buffer
                *pszBuffer++ = TEXT('\\');
                *pszBuffer++ = *pszPrinterName++;
            }
            else
            {
                // check the buffer size
                if( pszBuffer >= pszBufEnd )
                    break; // not enough space

                // we have space in the buffer
                *pszBuffer++ = *pszPrinterName++;
            }
        }

        if( 0 == *pszPrinterName )
        {
            // the buffer is long enough
            hr = S_OK;
        }
        else
        {
            // we hit the insufficent buffer error
            hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER );
        }

        // put the zero terminator
        *pszBuffer = 0;
    }

    return hr;
}

HRESULT _PrinterVerbCB(UINT uCmd, HWND hWnd, HMENU hMenu, LPARAM uID, LPVERBINFO pvi, UINT uFlags)
{
    HRESULT hr;
    IADs* pDsObject = NULL;
    LPTSTR pPrinterUNC = NULL;
    LPTSTR pBuffer = NULL;
    LPTSTR pPrinterName = NULL;
    UINT uAtSymbolsCount, uBufSize;
    INT i;
    VARIANT variant;
    USES_CONVERSION;
    DECLAREWAITCURSOR = GetCursor();

    TraceEnter(TRACE_VERBS, "_ComputerVerbCB");

    VariantInit(&variant);

    switch ( uCmd )
    {
        case MENUCMD_INITITEM:
        {
            // printers want the open verb as their default.
            if ( LOWORD(uID) == IDC_PRINTER_INSTALL )
            {
                TraceMsg("Install should be the default verb for printQueue objects");
                SetMenuDefaultItem(hMenu, HIWORD(uID), MF_BYCOMMAND);
            }

            // printer verbs only work on a single selection.
            if ( DPA_GetPtrCount(pvi->hdpaSelection) != 1 )
            {
                TraceMsg("Selection is != 1, so disabling verb");
                EnableMenuItem(hMenu, HIWORD(uID), MF_BYCOMMAND|MF_GRAYED);
            }

            break;
        }

        case MENUCMD_INVOKE:
        {
            LPWSTR pPath = (LPWSTR)DPA_GetPtr(pvi->hdpaSelection, 0);       // selection always 0
            TraceAssert(pPath);

            SetWaitCursor();

            hr = ADsOpenObject(pPath, pvi->pszUserName, pvi->pszPassword, 
                              (pvi->dwFlags & DSDSOF_SIMPLEAUTHENTICATE) ? 0:ADS_SECURE_AUTHENTICATION, 
                              IID_IADs, (LPVOID*)&pDsObject);

            FailGracefully(hr, "Failed to get pDsObject");

            // for Windows NT we can grab the UNC name and build a command line
            // we invoke the printUI dll using.  

            hr = pDsObject->Get(L"uNCName", &variant);
            FailGracefully(hr, "Failed to get UNC from the printer object");

            if ( V_VT(&variant) != VT_BSTR )
                ExitGracefully(hr, E_FAIL, "UNC is not a BSTR - whats with that?");

            hr = LocalAllocStringW2T(&pPrinterUNC, W2T(V_BSTR(&variant)));
            FailGracefully(hr, "Failed to copy the printerUNC");

            Trace(TEXT("printQueue object UNC: %s"), pPrinterUNC);

            // if this is the downlevel shell then load the PRINUI code and then
            // invoke the handler accordingly.

            hr = _PrinterRunDLLCountAtSymbols(pPrinterUNC, &uAtSymbolsCount);
            FailGracefully(hr, "Failed to count the @ symbols");

            uBufSize = lstrlen(pPrinterUNC) + uAtSymbolsCount + 1;
            hr = LocalAllocStringLen(&pPrinterName,  uBufSize);
            FailGracefully(hr, "Failed to copy the printerName");

            hr = _PrinterRunDLLFormatAtSymbols(pPrinterName, uBufSize, pPrinterUNC);
            FailGracefully(hr, "Failed to format printerName @ symbols ");

            // allocate the format buffer.
            hr = LocalAllocStringLen(&pBuffer, lstrlen(PRINT_FMT) + 
                                               lstrlen(PRINT_SWITCH_OPEN) + 
                                               lstrlen(PRINT_SWITCH_INSTALL) +
                                               lstrlen(pPrinterName) + 1 );
                                               
            FailGracefully(hr, "Failed to allocate format buffer");

            // now format the line...

            wsprintf(pBuffer, PRINT_FMT, pPrinterName);

            switch ( LOWORD(uID) )
            {
                case IDC_PRINTER_OPEN:
                    StrCat(pBuffer, PRINT_SWITCH_OPEN);
                    break;

                case IDC_PRINTER_INSTALL:
                    StrCat(pBuffer, PRINT_SWITCH_INSTALL);
                    break;
            }

            ResetWaitCursor();

            BOOL bRunCommand = TRUE;
            if( IDC_PRINTER_INSTALL == LOWORD(uID) && _PrinterCheckRestrictions(hWnd, REST_NOPRINTERADD) )
                bRunCommand = FALSE;

            if( bRunCommand )
            {
                Trace(TEXT("Invoking: rundll32.exe %s"), pBuffer);
                ShellExecute(NULL, NULL, TEXT("rundll32.exe"), pBuffer,  NULL, SW_SHOWNORMAL);
            }
        }
    }

    hr = S_OK;                  // success

exit_gracefully:

    if (FAILED(hr))
    {
        // we need to tell something to the user here.
        FormatMsgBox(hWnd, GLOBAL_HINSTANCE, IDS_TITLE, IDS_ERR_DSOPENOBJECT, MB_OK|MB_ICONERROR);
    }

    VariantClear(&variant);

    DoRelease(pDsObject);

    LocalFreeString(&pPrinterUNC);
    LocalFreeString(&pPrinterName);
    LocalFreeString(&pBuffer);

    ResetWaitCursor();

    TraceLeaveResult(hr);
}