#include "pch.h" #include "wab.h" #pragma hdrstop /*----------------------------------------------------------------------------- / Misc data /----------------------------------------------------------------------------*/ // // CDsPropertyPages is used to display the property pages, context menus etc // class CDsPropertyPages : public IWABExtInit, IShellExtInit, IContextMenu, IShellPropSheetExt, IObjectWithSite { private: LONG _cRef; IUnknown* _punkSite; IDataObject* _pDataObject; HDSA _hdsaMenuItems; SHORT AddMenuItem(HMENU hMenu, LPWSTR pMenuReference, UINT index, UINT uIDFirst, UINT uIDLast, UINT uFlags); public: CDsPropertyPages(); ~CDsPropertyPages(); // IUnknown members STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID, LPVOID FAR*); // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID); // IWABExtInit STDMETHODIMP Initialize(LPWABEXTDISPLAY pWED); // IShellPropSheetExt STDMETHODIMP AddPages(LPFNADDPROPSHEETPAGE pAddPageProc, LPARAM lParam); STDMETHODIMP ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pReplacePageFunc, LPARAM lParam); // 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); // IObjectWithSite STDMETHODIMP SetSite(IUnknown* punk); STDMETHODIMP GetSite(REFIID riid, void **ppv); }; // // To handle the conversion from a IWABExtInit to an IShellExtInit we must // provide an IDataObject implementation that supports this. This doesn't need // to be too public, therefore lets define it here. // class CWABDataObject : public IDataObject { private: LONG _cRef; LPWSTR _pPath; IADs* _pDsObject; public: CWABDataObject(LPWSTR pDN); ~CWABDataObject(); // IUnknown STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObject); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // IDataObject STDMETHODIMP GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium); STDMETHODIMP GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium) { return E_NOTIMPL; } STDMETHODIMP QueryGetData(FORMATETC *pformatetc) { return E_NOTIMPL; } STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut) { return E_NOTIMPL; } STDMETHODIMP SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) { return E_NOTIMPL; } STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) { return E_NOTIMPL; } STDMETHODIMP DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) { return E_NOTIMPL; } STDMETHODIMP DUnadvise(DWORD dwConnection) { return E_NOTIMPL; } STDMETHODIMP EnumDAdvise(IEnumSTATDATA **ppenumAdvise) { return E_NOTIMPL; } }; // // clipboard formats exposed // CLIPFORMAT g_cfDsObjectNames = 0; CLIPFORMAT g_cfDsDispSpecOptions = 0; // // Having extracted the menu item handler list from the cache we then // convert it DSA made of the following items. For // typedef struct { INT cAdded; // number of verbs added IContextMenu* pContextMenu; // IContextMenu handler interface / = NULL LPTSTR pCaption; // Display text for the command, used for the help text LPTSTR pCommand; // Command line passed to shell execute } DSMENUITEM, * LPDSMENUITEM; /*---------------------------------------------------------------------------- / Helper functions /----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------- / _FreeMenuItem / ------------- / Tidy up a DSMENUITEM structure, releasing all memory, interfaces etc. / / In: / pItem -> item to be released / / Out: / VOID /----------------------------------------------------------------------------*/ VOID _FreeMenuItem(LPDSMENUITEM pItem) { TraceEnter(TRACE_UI, "_FreeMenuItem"); // ensure we free the site object, or we will leak memory if (pItem->pContextMenu) { IObjectWithSite *pows; if (SUCCEEDED(pItem->pContextMenu->QueryInterface(IID_PPV_ARG(IObjectWithSite, &pows)))) { pows->SetSite(NULL); pows->Release(); } } DoRelease(pItem->pContextMenu); LocalFreeString(&pItem->pCaption); LocalFreeString(&pItem->pCommand); TraceLeave(); } // // Helper for DSA destruction // INT _FreeMenuItemCB(LPVOID pVoid, LPVOID pData) { LPDSMENUITEM pItem = (LPDSMENUITEM)pVoid; TraceAssert(pItem); TraceEnter(TRACE_UI, "_FreeMenuItemCB"); _FreeMenuItem(pItem); TraceLeaveValue(TRUE); } /*---------------------------------------------------------------------------- / CDsPropertyPages implementation /----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------- / IUnknown /----------------------------------------------------------------------------*/ CDsPropertyPages::CDsPropertyPages() : _cRef(1), _punkSite(NULL), _pDataObject(NULL), _hdsaMenuItems(NULL) { DllAddRef(); } CDsPropertyPages::~CDsPropertyPages() { DoRelease(_punkSite); DoRelease(_pDataObject); if (_hdsaMenuItems) DSA_DestroyCallback(_hdsaMenuItems, _FreeMenuItemCB, NULL); DllRelease(); } // IUnknown ULONG CDsPropertyPages::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CDsPropertyPages::Release() { TraceAssert( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CDsPropertyPages::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CDsPropertyPages, IShellExtInit), // IID_IShellExtInit QITABENT(CDsPropertyPages, IShellPropSheetExt), // IID_IShellPropSheetExt QITABENT(CDsPropertyPages, IContextMenu), // IID_IContextMenu QITABENT(CDsPropertyPages, IWABExtInit), // IID_IWABExtInit QITABENT(CDsPropertyPages, IObjectWithSite), // IID_IObjectWithSite {0, 0 }, }; return QISearch(this, qit, riid, ppv); } // // handle create instance // STDAPI CDsPropertyPages_CreateInstance(IUnknown* punkOuter, IUnknown** ppunk, LPCOBJECTINFO poi) { CDsPropertyPages *pdpp = new CDsPropertyPages(); if (!pdpp) return E_OUTOFMEMORY; HRESULT hres = pdpp->QueryInterface(IID_IUnknown, (void **)ppunk); pdpp->Release(); return hres; } /*----------------------------------------------------------------------------- / CDsPropertyPages::AddMenuItem / ----------------------------- / This object maintains a DSA containing the currently active menu item list, / this adds a menu item to that list and also merges with the specified / hMenu. We are given a string which reperesnets the menu to add, this / can either be a GUID, or "display text,command" which we then parse / and make a suitable entry for. / / The DSA reflects the items that we add and contains the IContextMenu / handler iface pointers for the things we drag in. / / In: / hMenu = menu to merge into / pMenuReference -> string defining item to add / index = index to insert the item at / uIDFirst, uIDLast, uFlags = IContextMenu::QueryContextMenu parameters / / Out: / SHORT = the number of items merged /----------------------------------------------------------------------------*/ SHORT CDsPropertyPages::AddMenuItem(HMENU hMenu, LPWSTR pMenuReference, UINT index, UINT uIDFirst, UINT uIDLast, UINT uFlags) { HRESULT hres; GUID guid; WCHAR szCaption[MAX_PATH]; WCHAR szCommand[MAX_PATH]; DSMENUITEM item; IShellExtInit* pShellExtInit = NULL; IObjectWithSite *pows = NULL; TraceEnter(TRACE_UI, "CDsPropertyPages::AddMenuItem"); // initialize the item structure we are going to keep, then try and crack the // item information we have been given item.cAdded = 0; item.pContextMenu = NULL; item.pCaption = NULL; item.pCommand = NULL; if (!hMenu) ExitGracefully(hres, E_INVALIDARG, "Bad arguments to _AddMenuItem"); if (GetGUIDFromString(pMenuReference, &guid)) { // its a GUID, therefore lets pull in the Win32 extension that provides it, and allow it // to add in its verbs. We then hang onto the IContextMenu interface so that we can // pass further requests to it (InvokeCommand, GetCommandString). hres = CoCreateInstance(guid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IContextMenu, &item.pContextMenu)); FailGracefully(hres, "Failed to get IContextMenu from the GUID"); if (_punkSite && SUCCEEDED(item.pContextMenu->QueryInterface(IID_PPV_ARG(IObjectWithSite, &pows)))) { hres = pows->SetSite(_punkSite); FailGracefully(hres, "Failed to ::SetSite on the extension object"); } if (SUCCEEDED(item.pContextMenu->QueryInterface(IID_PPV_ARG(IShellExtInit, &pShellExtInit)))) { hres = pShellExtInit->Initialize(NULL, _pDataObject, NULL); FailGracefully(hres, "Failed when calling IShellExtInit::Initialize"); } hres = item.pContextMenu->QueryContextMenu(hMenu, index, uIDFirst, uIDLast, uFlags); FailGracefully(hres, "Failed when calling QueryContextMenu"); item.cAdded = ShortFromResult(hres); } else { // its not a GUID therefore lets pull apart the string we have, it should // consist of the display text for the menu item, and then a command to pass // to ShellExecute. Trace(TEXT("Parsing: %s"), pMenuReference); if (SUCCEEDED(GetStringElementW(pMenuReference, 0, szCaption, ARRAYSIZE(szCaption))) && SUCCEEDED(GetStringElementW(pMenuReference, 1, szCommand, ARRAYSIZE(szCommand)))) { hres = LocalAllocStringW(&item.pCaption, szCaption); FailGracefully(hres, "Failed to add 'prompt' to structure"); hres = LocalAllocStringW(&item.pCommand, szCommand); FailGracefully(hres, "Failed to add 'command' to structure"); Trace(TEXT("uID: %08x, Caption: %s, Command: %s"), uIDFirst, item.pCaption, item.pCommand); if (!InsertMenu(hMenu, index, MF_BYPOSITION|MF_STRING, uIDFirst, item.pCaption)) ExitGracefully(hres, E_FAIL, "Failed to add the menu item to hMenu"); item.cAdded = 1; } } hres = S_OK; // success exit_gracefully: if (SUCCEEDED(hres)) { if (-1 == DSA_AppendItem(_hdsaMenuItems, &item)) ExitGracefully(hres, E_FAIL, "Failed to add the item to the DSA"); } else { _FreeMenuItem(&item); // make sure we tidy up } DoRelease(pows); DoRelease(pShellExtInit); TraceLeaveValue((SHORT)item.cAdded); } /*---------------------------------------------------------------------------- / IShellExtInit /----------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID) { HRESULT hres; TraceEnter(TRACE_UI, "CDsPropertyPages::Initialize (IShellExtInit)"); // Release the previous data object and then pick up the new one that // we are going to be using. DoRelease(_pDataObject); if (!pDataObj) ExitGracefully(hres, E_INVALIDARG, "Failed because we don't have a data object"); pDataObj->AddRef(); _pDataObject = pDataObj; // Check that we have the clipboard format correctly registered so that we // can collect a DSOBJECTNAMES structure if (!g_cfDsObjectNames) { g_cfDsObjectNames = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_DSOBJECTNAMES); g_cfDsDispSpecOptions = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_DSDISPLAYSPECOPTIONS); if (!g_cfDsObjectNames || !g_cfDsDispSpecOptions) { ExitGracefully(hres, E_FAIL, "No clipboard form registered"); } } hres = S_OK; // success exit_gracefully: TraceLeaveResult(hres); } /*---------------------------------------------------------------------------- / IWABExtInit /----------------------------------------------------------------------------*/ #define WAB_PREFIX L"ldap:///" #define CCH_WAB_PREFIX ARRAYSIZE(WAB_PREFIX)-1 STDMETHODIMP CDsPropertyPages::Initialize(LPWABEXTDISPLAY pWED) { HRESULT hres; WCHAR szDecodedURL[INTERNET_MAX_URL_LENGTH]; LPWSTR pszDecodedURL = szDecodedURL; INT cchDecodedURL; DWORD dwLen = ARRAYSIZE(szDecodedURL); IDataObject* pDataObject = NULL; LPWSTR pszPath = NULL; LPWSTR pURL = (LPWSTR)pWED->lpsz; INT i; TraceEnter(TRACE_UI, "CDsPropertyPages::Initialize (IWABExtInit)"); if (!(pWED->ulFlags & WAB_DISPLAY_ISNTDS)) ExitGracefully(hres, E_FAIL, "The URL is not from NTDS, therefore ignoring"); if (!pURL) ExitGracefully(hres, E_FAIL, "URL pointer is NULL"); Trace(TEXT("LDAP URL is: %s"), pURL); // // we must now convert from a RFC LDAP URL to something that ADSI can handle, because // although they both have the LDAP scheme they don't really mean the same thing. // // WAB will pass us an encoded URL, this we need to decode, strip the scheme name and // then remove the tripple slash, // // eg: "LDAP:///dn%20dn" becomes, "LDAP://dn dn" // hres = UrlUnescapeW(pURL, szDecodedURL, &dwLen, 0); FailGracefully(hres, "Failed to convert URL to decoded format"); Trace(TEXT("Decoded URL is: %s"), szDecodedURL); pszDecodedURL += CCH_WAB_PREFIX; // skip the LDAP:/// // // now tail the URL removing all trailing slashes from it // for (cchDecodedURL = lstrlenW(pszDecodedURL); (cchDecodedURL > 0) && (pszDecodedURL[cchDecodedURL] == L'/'); cchDecodedURL--) { pszDecodedURL[cchDecodedURL] = L'\0'; } if (!cchDecodedURL) ExitGracefully(hres, E_UNEXPECTED, "URL is now NULL"); // // so we have a DN, so lets allocate a IDataObject using it so that we // can pass this into the real initialize method for shell extensions. // Trace(TEXT("DN from the LDAP URL we were given: %s"), pszDecodedURL); pDataObject = new CWABDataObject(pszDecodedURL); TraceAssert(pDataObject); if (!pDataObject) ExitGracefully(hres, E_OUTOFMEMORY, "Failed to allocate the data object"); hres = Initialize(NULL, pDataObject, NULL); FailGracefully(hres, "Failed to initialize with the IDataObject"); // hres = S_OK; // success exit_gracefully: DoRelease(pDataObject); TraceLeaveResult(hres); } /*---------------------------------------------------------------------------- / IShellPropSheetExt /----------------------------------------------------------------------------*/ HRESULT TabCollector_Collect(IUnknown *punkSite, IDataObject* pDataObject, LPFNADDPROPSHEETPAGE pAddPageProc, LPARAM lParam); STDMETHODIMP CDsPropertyPages::AddPages(LPFNADDPROPSHEETPAGE pAddPageProc, LPARAM lParam) { HRESULT hres; TraceEnter(TRACE_UI, "CDsPropertyPages::AddPages"); hres = TabCollector_Collect(_punkSite, _pDataObject, pAddPageProc, lParam); FailGracefully(hres, "Failed when calling the collector"); //hres = S_OK; // success exit_gracefully: TraceLeaveResult(hres); } /*---------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE lpfnReplaceWith, LPARAM lParam) { TraceEnter(TRACE_UI, "CDsPropertyPages::ReplacePage"); TraceLeaveResult(E_NOTIMPL); } /*---------------------------------------------------------------------------- / IContextMenu /----------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::QueryContextMenu(HMENU hMenu, UINT index, UINT uIDFirst, UINT uIDLast, UINT uFlags) { HRESULT hres; STGMEDIUM medium = { TYMED_NULL }; FORMATETC fmte = {g_cfDsObjectNames, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; LPDSOBJECTNAMES pDsObjectNames = NULL; LPWSTR pPath; LPWSTR pObjectClass; CLASSCACHEGETINFO ccgi = { 0 }; LPCLASSCACHEENTRY pCacheEntry = NULL; INT i; INT cAdded = 0; TraceEnter(TRACE_UI, "CDsPropertyPages::QueryContextMenu"); if (!hMenu || !_pDataObject) ExitGracefully(hres, E_FAIL, "Either no IDataObject or no hMenu"); // Get the bits of information we need from the data object, we are not // interested in a attributePrefix, therefore we skip that bit // and then look up the menu list in the cache. hres = _pDataObject->GetData(&fmte, &medium); FailGracefully(hres, "Failed to GetData using CF_DSOBJECTNAMES"); pDsObjectNames = (LPDSOBJECTNAMES)GlobalLock(medium.hGlobal); if (pDsObjectNames->cItems < 1) ExitGracefully(hres, E_FAIL, "Not enough objects in DSOBJECTNAMES structure"); pPath = (LPWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[0].offsetName); pObjectClass = (LPWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[0].offsetClass); // fill the CLASSCACHEGETINFO record so we can cache the information from the // display specifiers. ccgi.dwFlags = CLASSCACHE_CONTEXTMENUS; ccgi.pPath = pPath; ccgi.pObjectClass = pObjectClass; ccgi.pDataObject = _pDataObject; hres = GetServerAndCredentails(&ccgi); FailGracefully(hres, "Failed to get the server name"); hres = GetAttributePrefix(&ccgi.pAttributePrefix, _pDataObject); FailGracefully(hres, "Failed to get attributePrefix"); Trace(TEXT("Class: %s; Attribute Prefix: %s; Server: %s"), pObjectClass, ccgi.pAttributePrefix, ccgi.pServer ? ccgi.pServer:TEXT("")); hres = ClassCache_GetClassInfo(&ccgi, &pCacheEntry); FailGracefully(hres, "Failed to get page list (via the cache)"); // did we get a menu list? If so lets pull it a part and generate a DSA // which lists the menu items we are going to be displaying. if ((pCacheEntry->dwCached & CLASSCACHE_CONTEXTMENUS) && pCacheEntry->hdsaMenuHandlers) { if (_hdsaMenuItems) DSA_DestroyCallback(_hdsaMenuItems, _FreeMenuItemCB, NULL); _hdsaMenuItems = DSA_Create(SIZEOF(DSMENUITEM), 4); if (!_hdsaMenuItems) ExitGracefully(hres, E_OUTOFMEMORY, "Failed to construct DSA for menu items"); for (i = DSA_GetItemCount(pCacheEntry->hdsaMenuHandlers) ; --i >= 0 ;) { LPDSMENUHANDLER pHandlerItem = (LPDSMENUHANDLER)DSA_GetItemPtr(pCacheEntry->hdsaMenuHandlers, i); TraceAssert(pHandlerItem); cAdded += AddMenuItem(hMenu, pHandlerItem->pMenuReference, index, uIDFirst+cAdded, uIDLast, uFlags); } } hres = S_OK; // success exit_gracefully: LocalFreeStringW(&ccgi.pAttributePrefix); SecureLocalFreeStringW(&ccgi.pUserName); SecureLocalFreeStringW(&ccgi.pPassword); SecureLocalFreeStringW(&ccgi.pServer); ClassCache_ReleaseClassInfo(&pCacheEntry); if (pDsObjectNames) GlobalUnlock(medium.hGlobal); ReleaseStgMedium(&medium); TraceLeaveResult(ResultFromShort(cAdded)); } /*---------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::InvokeCommand(LPCMINVOKECOMMANDINFO pCMI) { HRESULT hres; BOOL bReleaseMedium = FALSE; STGMEDIUM medium = { TYMED_NULL }; FORMATETC fmte = {g_cfDsObjectNames, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; LPTSTR pArguments = NULL; LPDSOBJECTNAMES pDsObjectNames; LPTSTR pPath; LPWSTR pObjectClass; DWORD object; INT i, id; TraceEnter(TRACE_UI, "CDsPropertyPages::InvokeCommand"); // Walk the DSA until we find an item in it that contains the range of // items we are looking for, this will either involve invoking the // command (via IContextMenu::InvokeCommand) or calling ShellExecute // for the objects in the selection. if (HIWORD(pCMI->lpVerb)) ExitGracefully(hres, E_FAIL, "Bad lpVerb value for this handler"); if (!_hdsaMenuItems) ExitGracefully(hres, E_INVALIDARG, "No menu item DSA"); for (id = LOWORD(pCMI->lpVerb), i = 0 ; i < DSA_GetItemCount(_hdsaMenuItems) ; i++) { LPDSMENUITEM pItem = (LPDSMENUITEM)DSA_GetItemPtr(_hdsaMenuItems, i); TraceAssert(pItem); Trace(TEXT("id %08x, cAdded %d"), id, pItem->cAdded); if (id < pItem->cAdded) { if (pItem->pContextMenu) { CMINVOKECOMMANDINFO cmi = *pCMI; cmi.lpVerb = (LPCSTR)IntToPtr(id); Trace(TEXT("Calling IContextMenu iface with ID %d"), id); hres = pItem->pContextMenu->InvokeCommand(&cmi); FailGracefully(hres, "Failed when calling context menu handler (InvokeCommand)"); } else { // the command is not serviced via an IContextMenu handler, therefore lets for // each object in the IDataObject call the command passing the arguments of // the ADsPath and the class. hres = _pDataObject->GetData(&fmte, &medium); FailGracefully(hres, "Failed to GetData using CF_DSOBJECTNAMES"); pDsObjectNames = (LPDSOBJECTNAMES)GlobalLock(medium.hGlobal); bReleaseMedium = TRUE; if (pDsObjectNames->cItems < 1) ExitGracefully(hres, E_FAIL, "Not enough objects in DSOBJECTNAMES structure"); Trace(TEXT("Calling ShellExecute for ID %d (%s)"), id, pItem->pCommand); for (object = 0 ; object < pDsObjectNames->cItems ; object++) { pPath = (LPWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[object].offsetName); pObjectClass = (LPWSTR)ByteOffset(pDsObjectNames, pDsObjectNames->aObjects[object].offsetClass); int cchArguments = lstrlen(pPath)+lstrlenW(pObjectClass)+5; // nb: +5 for space and quotes hres = LocalAllocStringLen(&pArguments, cchArguments); FailGracefully(hres, "Failed to allocate buffer for arguments"); // does the object path have a space? if so then lets wrap it in quotes if (StrChr(pPath, TEXT(' '))) { StrCpyN(pArguments, TEXT("\""), cchArguments); StrCatBuff(pArguments, pPath, cchArguments); StrCatBuff(pArguments, TEXT("\""), cchArguments); } else { StrCpyN(pArguments, pPath, cchArguments); } StrCatBuff(pArguments, TEXT(" "), cchArguments); StrCatBuff(pArguments, pObjectClass, cchArguments); Trace(TEXT("Executing: %s"), pItem->pCommand); Trace(TEXT("Arguments: %s"), pArguments); // calls ShellExecute with a command from the Display Specifier string. ShellExecute(NULL, NULL, pItem->pCommand, pArguments, NULL, SW_SHOWNORMAL); LocalFreeString(&pArguments); } GlobalUnlock(medium.hGlobal); ReleaseStgMedium(&medium); bReleaseMedium = FALSE; } break; } id -= pItem->cAdded; } hres = (i < DSA_GetItemCount(_hdsaMenuItems)) ? S_OK:E_FAIL; exit_gracefully: if (bReleaseMedium) { GlobalUnlock(medium.hGlobal); ReleaseStgMedium(&medium); } LocalFreeString(&pArguments); TraceLeaveResult(hres); } /*---------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::GetCommandString(UINT_PTR uID, UINT uFlags, UINT FAR* reserved, LPSTR pName, UINT ccNameMax) { HRESULT hres; INT i; INT id = (INT)uID; TraceEnter(TRACE_UI, "CDsPropertyPages::GetCommandString"); // Walk down the list of the menu items looking for one that matches the // item we are trying get the command string from. If it is an IContextMenu // handler then we must call down to that. if (!_hdsaMenuItems) ExitGracefully(hres, E_INVALIDARG, "No menu item DSA"); for (i = 0 ; i < DSA_GetItemCount(_hdsaMenuItems) ; i++) { LPDSMENUITEM pItem = (LPDSMENUITEM)DSA_GetItemPtr(_hdsaMenuItems, i); TraceAssert(pItem); Trace(TEXT("id %08x, cAdded %d"), id, pItem->cAdded); if (id < pItem->cAdded) { if (pItem->pContextMenu) { hres = pItem->pContextMenu->GetCommandString(id, uFlags, reserved, pName, ccNameMax); FailGracefully(hres, "Failed when calling context menu handler (GetCommandString)"); } else { if (uFlags != GCS_HELPTEXT) ExitGracefully(hres, E_FAIL, "We only respond to GCS_HELPTEXT"); Trace(TEXT("GCS_HELPTEXT returns for non-IContextMenu item: %s"), pItem->pCaption); StrCpyN((LPTSTR)pName, pItem->pCaption, ccNameMax); } break; } id -= pItem->cAdded; } hres = (i < DSA_GetItemCount(_hdsaMenuItems)) ? S_OK:E_FAIL; exit_gracefully: TraceLeaveResult(hres); } /*---------------------------------------------------------------------------- / IObjectWithSite /----------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::SetSite(IUnknown* punk) { HRESULT hres = S_OK; TraceEnter(TRACE_UI, "CDsPropertyPages::SetSite"); DoRelease(_punkSite); if (punk) { TraceMsg("QIing for IUnknown from the site object"); hres = punk->QueryInterface(IID_IUnknown, (void **)&_punkSite); FailGracefully(hres, "Failed to get IUnknown from the site object"); } exit_gracefully: TraceLeaveResult(hres); } /*---------------------------------------------------------------------------*/ STDMETHODIMP CDsPropertyPages::GetSite(REFIID riid, void **ppv) { HRESULT hres; TraceEnter(TRACE_UI, "CDsPropertyPages::GetSite"); if (!_punkSite) ExitGracefully(hres, E_NOINTERFACE, "No site to QI from"); hres = _punkSite->QueryInterface(riid, ppv); FailGracefully(hres, "QI failed on the site unknown object"); exit_gracefully: TraceLeaveResult(hres); } /*----------------------------------------------------------------------------- / CWABDataObject /----------------------------------------------------------------------------*/ CWABDataObject::CWABDataObject(LPWSTR pDN) : _cRef(1) { TraceEnter(TRACE_WAB, "CWABDataObject::CWABDataObject"); int cchPath = lstrlenW(pDN)+7; // +7 for LDAP:// if (SUCCEEDED(LocalAllocStringLenW(&_pPath, cchPath))) { StrCpyW(_pPath, L"LDAP://"); StrCatW(_pPath, pDN); Trace(TEXT("DN converted to an ADSI path: %s"), _pPath); } DllAddRef(); TraceLeave(); } CWABDataObject::~CWABDataObject() { TraceEnter(TRACE_WAB, "CWABDataObject::~CWABDataObject"); LocalFreeStringW(&_pPath); DoRelease(_pDsObject); DllRelease(); TraceLeave(); } // IUnknown ULONG CWABDataObject::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CWABDataObject::Release() { TraceAssert( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CWABDataObject::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CWABDataObject, IDataObject), // IID_IDataObject {0, 0 }, }; return QISearch(this, qit, riid, ppv); } // IDataObject methods STDMETHODIMP CWABDataObject::GetData(FORMATETC* pFmt, STGMEDIUM* pMedium) { HRESULT hres; BOOL bReleaseMedium = FALSE; BSTR bstrObjectClass = NULL; DWORD cbStruct = SIZEOF(DSOBJECTNAMES); DWORD offset = SIZEOF(DSOBJECTNAMES); LPDSOBJECTNAMES pDsObjectNames = NULL; CLASSCACHEGETINFO ccgi = { 0 }; CLASSCACHEENTRY *pcce = NULL; TraceEnter(TRACE_WAB, "CWABDataObject::GetData"); if (!g_cfDsObjectNames) ExitGracefully(hres, E_FAIL, "g_cfDsObjectNames == NULL, therefore GetData cannot work"); if (!_pPath) ExitGracefully(hres, E_FAIL, "No _pPath set in data object"); if (pFmt->cfFormat == g_cfDsObjectNames) { // do we have the ADsObject that represents this path yet? If not then // lets grab it, but only do that once otherwise we will continually hit // the wire. if (!_pDsObject) { Trace(TEXT("Caching IADs for %s"), _pPath); hres = AdminToolsOpenObject(_pPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_PPV_ARG(IADs, &_pDsObject)); FailGracefully(hres, "Failed to get IADs for ADsPath we have"); } // lets allocate a storage medium, put in the only object we have // and then return that to the caller. hres = _pDsObject->get_Class(&bstrObjectClass); FailGracefully(hres, "Failed to get the class of the object"); // we have the information we need so lets allocate the storage medium and // return the DSOBJECTNAMES structure to the caller. cbStruct += StringByteSizeW(_pPath); cbStruct += StringByteSizeW(bstrObjectClass); hres = AllocStorageMedium(pFmt, pMedium, cbStruct, (LPVOID*)&pDsObjectNames); FailGracefully(hres, "Failed to allocate storage medium"); bReleaseMedium = TRUE; pDsObjectNames->clsidNamespace = CLSID_MicrosoftDS; pDsObjectNames->cItems = 1; pDsObjectNames->aObjects[0].dwFlags = 0; // check to see if the object is a container, if it is then set the attributes // accordingly. ccgi.dwFlags = CLASSCACHE_CONTAINER|CLASSCACHE_TREATASLEAF; ccgi.pPath = _pPath; ccgi.pObjectClass = bstrObjectClass; hres = ClassCache_GetClassInfo(&ccgi, &pcce); if (SUCCEEDED(hres)) { if (_IsClassContainer(pcce, FALSE)) { TraceMsg("Flagging the object as a container"); pDsObjectNames->aObjects[0].dwFlags |= DSOBJECT_ISCONTAINER; } ClassCache_ReleaseClassInfo(&pcce); } pDsObjectNames->aObjects[0].dwProviderFlags = 0; pDsObjectNames->aObjects[0].offsetName = offset; StringByteCopyW(pDsObjectNames, offset, _pPath); offset += StringByteSizeW(_pPath); pDsObjectNames->aObjects[0].offsetClass = offset; StringByteCopyW(pDsObjectNames, offset, bstrObjectClass); offset += StringByteSizeW(bstrObjectClass); } else if (pFmt->cfFormat == g_cfDsDispSpecOptions) { PDSDISPLAYSPECOPTIONS pOptions; DWORD cbSize = SIZEOF(DSDISPLAYSPECOPTIONS)+StringByteSizeW(DS_PROP_SHELL_PREFIX); // return the display spec options so we can indicate that WAB is involved // in the menus. hres = AllocStorageMedium(pFmt, pMedium, cbSize, (LPVOID*)&pOptions); FailGracefully(hres, "Failed to allocate the storage medium"); bReleaseMedium = TRUE; pOptions->dwSize = cbSize; pOptions->dwFlags = DSDSOF_INVOKEDFROMWAB; // invoked from WAB however pOptions->offsetAttribPrefix = SIZEOF(DSDISPLAYSPECOPTIONS); StringByteCopyW(pOptions, pOptions->offsetAttribPrefix, DS_PROP_SHELL_PREFIX); } else { ExitGracefully(hres, DV_E_FORMATETC, "Bad format passed to GetData"); } hres = S_OK; // success exit_gracefully: if (FAILED(hres) && bReleaseMedium) ReleaseStgMedium(pMedium); SysFreeString(bstrObjectClass); TraceLeaveResult(hres); }