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