You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1037 lines
33 KiB
1037 lines
33 KiB
#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("<none>"));
|
|
|
|
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);
|
|
}
|