1017 lines
33 KiB
C++
1017 lines
33 KiB
C++
#include "pch.h"
|
|
#include "stddef.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ Query thread bits
|
|
/----------------------------------------------------------------------------*/
|
|
|
|
//
|
|
// Page size used for paging the result sets (the LDAP server core is a sync process)
|
|
// therefore getting it to return smaller result blobs is better for us
|
|
//
|
|
|
|
#define PAGE_SIZE 64
|
|
#define MAX_RESULT 10000
|
|
|
|
|
|
//
|
|
// When the query is issued we always pull back at least ADsPath and objectClass
|
|
// as properites (these are required for the viewer to work). Therefore these define
|
|
// the mapping of these values to the returned column set.
|
|
//
|
|
|
|
#define PROPERTYMAP_ADSPATH 0
|
|
#define PROPERTYMAP_OBJECTCLASS 1
|
|
#define PROPERTYMAP_USER 2
|
|
|
|
#define INDEX_TO_PROPERTY(i) (i+PROPERTYMAP_USER)
|
|
|
|
|
|
//
|
|
// THREADDATA this is the state structure for the thread, it encapsulates
|
|
// the parameters and other junk required to the keep the thread alive.
|
|
//
|
|
|
|
typedef struct
|
|
{
|
|
LPTHREADINITDATA ptid;
|
|
INT cProperties; // number of properties in aProperties
|
|
LPWSTR* aProperties; // array of string pointers for "property names"
|
|
INT cColumns; // number of columsn in view
|
|
INT* aColumnToPropertyMap; // mapping from display column index to property name
|
|
} THREADDATA, * LPTHREADDATA;
|
|
|
|
|
|
//
|
|
// Helper macro to send messages for the fg view, including the reference
|
|
// count
|
|
//
|
|
|
|
#define SEND_VIEW_MESSAGE(ptid, uMsg, lParam) \
|
|
SendMessage(GetParent(ptid->hwndView), uMsg, (ptid)->dwReference, lParam)
|
|
|
|
//
|
|
// Function prototypes for the query thread engine.
|
|
//
|
|
|
|
HRESULT QueryThread_IssueQuery(LPTHREADDATA ptd);
|
|
HRESULT QueryThread_BuildPropertyList(LPTHREADDATA ptd);
|
|
VOID QueryThread_FreePropertyList(LPTHREADDATA ptd);
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ Helper functions
|
|
/----------------------------------------------------------------------------*/
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread_GetFilter
|
|
/ ----------------------
|
|
/ Construct the LDAP filter we are going to use for this query.
|
|
/
|
|
/ In:
|
|
/ ppQuery -> receives the full filter
|
|
/ pBaseFilter -> filter string to use as base
|
|
/ fShowHidden = show hidden objects?
|
|
/
|
|
/ Out:
|
|
/ HRESULT
|
|
/----------------------------------------------------------------------------*/
|
|
|
|
VOID _GetFilter(LPWSTR pFilter, UINT* pcchFilterLen, LPWSTR pBaseFilter, BOOL fShowHidden)
|
|
{
|
|
HRESULT hr;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "_GetFilter");
|
|
|
|
if (pFilter)
|
|
*pFilter = TEXT('\0');
|
|
|
|
PutStringElementW(pFilter, pcchFilterLen, L"(&");
|
|
|
|
if (!fShowHidden)
|
|
PutStringElementW(pFilter, pcchFilterLen, c_szShowInAdvancedViewOnly);
|
|
|
|
PutStringElementW(pFilter, pcchFilterLen, pBaseFilter);
|
|
PutStringElementW(pFilter, pcchFilterLen, L")");
|
|
|
|
TraceLeave();
|
|
}
|
|
|
|
HRESULT QueryThread_GetFilter(LPWSTR* ppFilter, LPWSTR pBaseFilter, BOOL fShowHidden)
|
|
{
|
|
HRESULT hr;
|
|
UINT cchFilterLen = 0;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "QueryThread_GetFilter");
|
|
|
|
_GetFilter(NULL, &cchFilterLen, pBaseFilter, fShowHidden);
|
|
|
|
hr = LocalAllocStringLenW(ppFilter, cchFilterLen);
|
|
FailGracefully(hr, "Failed to allocate buffer for query string");
|
|
|
|
_GetFilter(*ppFilter, NULL, pBaseFilter, fShowHidden);
|
|
|
|
hr = S_OK;
|
|
|
|
exit_gracefully:
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ Background query thread, this does the work of issuing the query and then
|
|
/ populating the view.
|
|
/----------------------------------------------------------------------------*/
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread
|
|
/ -----------
|
|
/ Thread function sits spinning its wheels waiting for a query to be
|
|
/ received from the outside world. The main result viewer communicates
|
|
/ with this code by ThreadSendMessage.
|
|
/
|
|
/ In:
|
|
/ pThreadParams -> structure that defines out thread information
|
|
/
|
|
/ Out:
|
|
/ -
|
|
/----------------------------------------------------------------------------*/
|
|
DWORD WINAPI QueryThread(LPVOID pThreadParams)
|
|
{
|
|
HRESULT hresCoInit;
|
|
MSG msg;
|
|
LPTHREADINITDATA pThreadInitData = (LPTHREADINITDATA)pThreadParams;
|
|
THREADDATA td;
|
|
|
|
ZeroMemory(&td, SIZEOF(td));
|
|
|
|
td.ptid = pThreadInitData;
|
|
//td.cProperties = 0;
|
|
//td.aProperties = NULL;
|
|
//td.cColumns = 0;
|
|
//td.aColumnToPropertyMap = NULL;
|
|
|
|
hresCoInit = CoInitialize(NULL);
|
|
FailGracefully(hresCoInit, "Failed to CoInitialize");
|
|
|
|
GetActiveWindow(); // ensure we have amessage queue
|
|
|
|
QueryThread_IssueQuery(&td);
|
|
|
|
while (GetMessage(&msg, NULL, 0, 0) > 0)
|
|
{
|
|
switch (msg.message)
|
|
{
|
|
case RVTM_STOPQUERY:
|
|
TraceMsg("RVTM_STOPQUERY received - ignoring");
|
|
break;
|
|
|
|
case RVTM_REFRESH:
|
|
{
|
|
td.ptid->dwReference = (DWORD)msg.wParam;
|
|
QueryThread_IssueQuery(&td);
|
|
break;
|
|
}
|
|
|
|
case RVTM_SETCOLUMNTABLE:
|
|
{
|
|
if (td.ptid->hdsaColumns)
|
|
DSA_DestroyCallback(td.ptid->hdsaColumns, FreeColumnCB, NULL);
|
|
|
|
td.ptid->dwReference = (DWORD)msg.wParam;
|
|
td.ptid->hdsaColumns = (HDSA)msg.lParam;
|
|
|
|
QueryThread_FreePropertyList(&td);
|
|
QueryThread_IssueQuery(&td);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
QueryThread_FreePropertyList(&td);
|
|
QueryThread_FreeThreadInitData(&td.ptid);
|
|
|
|
if (SUCCEEDED(hresCoInit))
|
|
CoUninitialize();
|
|
|
|
DllRelease();
|
|
ExitThread(0);
|
|
return 0; // BOGUS: not never called
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread_FreeThreadInitData
|
|
/ ------------------------------
|
|
/ Release the THREADINITDATA structure that we are given when the thread
|
|
/ is created.
|
|
/
|
|
/ In:
|
|
/ pptid ->-> thread init data structure to be released
|
|
/
|
|
/ Out:
|
|
/ -
|
|
/----------------------------------------------------------------------------*/
|
|
VOID QueryThread_FreeThreadInitData(LPTHREADINITDATA* pptid)
|
|
{
|
|
LPTHREADINITDATA ptid = *pptid;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "QueryThread_FreeThreadInitData");
|
|
|
|
if (ptid)
|
|
{
|
|
LocalFreeStringW(&ptid->pQuery);
|
|
LocalFreeStringW(&ptid->pScope);
|
|
|
|
if (ptid->hdsaColumns)
|
|
DSA_DestroyCallback(ptid->hdsaColumns, FreeColumnCB, NULL);
|
|
|
|
LocalFreeStringW(&ptid->pServer);
|
|
LocalFreeStringW(&ptid->pUserName);
|
|
LocalFreeStringW(&ptid->pPassword);
|
|
|
|
LocalFree((HLOCAL)ptid);
|
|
*pptid = NULL;
|
|
}
|
|
|
|
TraceLeave();
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread_CheckForStopQuery
|
|
/ -----------------------------
|
|
/ Peek the message queue looking for a stop query message, if we
|
|
/ can find one then we must bail out.
|
|
/
|
|
/ In:
|
|
/ ptd -> thread data structure
|
|
/
|
|
/ Out:
|
|
/ fStopQuery
|
|
/----------------------------------------------------------------------------*/
|
|
BOOL QueryThread_CheckForStopQuery(LPTHREADDATA ptd)
|
|
{
|
|
BOOL fStopQuery = FALSE;
|
|
MSG msg;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "QueryThread_CheckForStopQuery");
|
|
|
|
while (PeekMessage(&msg, NULL, RVTM_FIRST, RVTM_LAST, PM_REMOVE))
|
|
{
|
|
TraceMsg("Found a RVTM_ message in queue, stopping query");
|
|
fStopQuery = TRUE;
|
|
}
|
|
|
|
TraceLeaveValue(fStopQuery);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread_IssueQuery
|
|
/ ----------------------
|
|
/ Issue a query using the IDirectorySearch interface, this is a more performant
|
|
/ to the wire interface that issues the query directly. The code binds to
|
|
/ the scope object (the specified path) and then issues the LDAP query
|
|
/ pumping the results into the viewer as required.
|
|
/
|
|
/ In:
|
|
/ ptd -> thread information structurre
|
|
/
|
|
/ Out:
|
|
/ HRESULT
|
|
/----------------------------------------------------------------------------*/
|
|
HRESULT QueryThread_IssueQuery(LPTHREADDATA ptd)
|
|
{
|
|
HRESULT hr;
|
|
DWORD dwres;
|
|
LPTHREADINITDATA ptid = ptd->ptid;
|
|
LPWSTR pQuery = NULL;
|
|
INT cItems = 0, iColumn;
|
|
INT cMaxResult = MAX_RESULT;
|
|
BOOL fStopQuery = FALSE;
|
|
IDirectorySearch* pDsSearch = NULL;
|
|
LPWSTR pszTempPath = NULL;
|
|
IDsDisplaySpecifier *pdds = NULL;
|
|
ADS_SEARCH_HANDLE hSearch = NULL;
|
|
ADS_SEARCHPREF_INFO prefInfo[3];
|
|
ADS_SEARCH_COLUMN column;
|
|
HDPA hdpaResults = NULL;
|
|
LPQUERYRESULT pResult = NULL;
|
|
WCHAR szBuffer[2048]; // MAX_URL_LENGHT
|
|
INT resid;
|
|
LPWSTR pColumnData = NULL;
|
|
HKEY hkPolicy = NULL;
|
|
USES_CONVERSION;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "QueryThread_IssueQuery");
|
|
|
|
// The foreground gave us a query so we are going to go and issue
|
|
// it now, having done this we will then be able to stream the
|
|
// result blobs back to the caller.
|
|
|
|
hr = QueryThread_GetFilter(&pQuery, ptid->pQuery, ptid->fShowHidden);
|
|
FailGracefully(hr, "Failed to build LDAP query from scope, parameters + filter");
|
|
|
|
Trace(TEXT("Query is: %s"), W2T(pQuery));
|
|
Trace(TEXT("Scope is: %s"), W2T(ptid->pScope));
|
|
|
|
// Get the IDsDisplaySpecifier interface:
|
|
|
|
hr = CoCreateInstance(CLSID_DsDisplaySpecifier, NULL, CLSCTX_INPROC_SERVER, IID_IDsDisplaySpecifier, (void **)&pdds);
|
|
FailGracefully(hr, "Failed to get the IDsDisplaySpecifier object");
|
|
|
|
hr = pdds->SetServer(ptid->pServer, ptid->pUserName, ptid->pPassword, DSSSF_DSAVAILABLE);
|
|
FailGracefully(hr, "Failed to server information");
|
|
|
|
// initialize the query engine, specifying the scope, and the search parameters
|
|
|
|
hr = QueryThread_BuildPropertyList(ptd);
|
|
FailGracefully(hr, "Failed to build property array to query for");
|
|
|
|
hr = ADsOpenObject(ptid->pScope, ptid->pUserName, ptid->pPassword, ADS_SECURE_AUTHENTICATION,
|
|
IID_IDirectorySearch, (LPVOID*)&pDsSearch);
|
|
|
|
FailGracefully(hr, "Failed to get the IDirectorySearch interface for the given scope");
|
|
|
|
prefInfo[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE; // sub-tree search
|
|
prefInfo[0].vValue.dwType = ADSTYPE_INTEGER;
|
|
prefInfo[0].vValue.Integer = ADS_SCOPE_SUBTREE;
|
|
|
|
prefInfo[1].dwSearchPref = ADS_SEARCHPREF_ASYNCHRONOUS; // async
|
|
prefInfo[1].vValue.dwType = ADSTYPE_BOOLEAN;
|
|
prefInfo[1].vValue.Boolean = TRUE;
|
|
|
|
prefInfo[2].dwSearchPref = ADS_SEARCHPREF_PAGESIZE; // paged results
|
|
prefInfo[2].vValue.dwType = ADSTYPE_INTEGER;
|
|
prefInfo[2].vValue.Integer = PAGE_SIZE;
|
|
|
|
hr = pDsSearch->SetSearchPreference(prefInfo, ARRAYSIZE(prefInfo));
|
|
FailGracefully(hr, "Failed to set search preferences");
|
|
|
|
hr = pDsSearch->ExecuteSearch(pQuery, ptd->aProperties, ptd->cProperties, &hSearch);
|
|
FailGracefully(hr, "Failed in ExecuteSearch");
|
|
|
|
// pick up the policy value which defines the max results we are going to use
|
|
|
|
dwres = RegOpenKey(HKEY_CURRENT_USER, DS_POLICY, &hkPolicy);
|
|
if (ERROR_SUCCESS == dwres)
|
|
{
|
|
DWORD dwType, cbSize;
|
|
|
|
dwres = RegQueryValueEx(hkPolicy, TEXT("QueryLimit"), NULL, &dwType, NULL, &cbSize);
|
|
if ((ERROR_SUCCESS == dwres) && (dwType == REG_DWORD) && (cbSize == SIZEOF(cMaxResult)))
|
|
{
|
|
RegQueryValueEx(hkPolicy, TEXT("QueryLimit"), NULL, NULL, (LPBYTE)&cMaxResult, &cbSize);
|
|
}
|
|
|
|
RegCloseKey(hkPolicy);
|
|
}
|
|
|
|
|
|
// issue the query, pumping the results to the foreground UI which
|
|
// will inturn populate the the list view
|
|
|
|
Trace(TEXT("Result limit set to %d"), cMaxResult);
|
|
|
|
for (cItems = 0 ; cItems < cMaxResult; cItems++)
|
|
{
|
|
ADsSetLastError(ERROR_SUCCESS, NULL, NULL); // clear the ADSI previous errror
|
|
|
|
hr = pDsSearch->GetNextRow(hSearch);
|
|
|
|
fStopQuery = QueryThread_CheckForStopQuery(ptd);
|
|
Trace(TEXT("fStopQuery %d, hr %08x"), fStopQuery, hr);
|
|
|
|
if (fStopQuery || (hr == S_ADS_NOMORE_ROWS))
|
|
{
|
|
DWORD dwError;
|
|
WCHAR wszError[64], wszName[64];
|
|
|
|
hr = ADsGetLastError(&dwError, wszError, ARRAYSIZE(wszError), wszName, ARRAYSIZE(wszName));
|
|
if (SUCCEEDED(hr) && (dwError != ERROR_MORE_DATA))
|
|
{
|
|
break;
|
|
}
|
|
|
|
hr = S_OK; // we have more data so lets continue
|
|
}
|
|
|
|
FailGracefully(hr, "Failed in GetNextRow");
|
|
|
|
// We have a result, lets ensure that we have posted the blob
|
|
// we are building before we start constructing a new one. We
|
|
// send pages of items to the fg thread for it to add to the
|
|
// result view, if the blob returns FALSE then we must tidy the
|
|
// DPA before continuing.
|
|
|
|
if (((cItems % 10) == 0) && hdpaResults) // 10 is a nice block size
|
|
{
|
|
TraceMsg("Posting results blob to fg thread");
|
|
|
|
if (!SEND_VIEW_MESSAGE(ptid, DSQVM_ADDRESULTS, (LPARAM)hdpaResults))
|
|
DPA_DestroyCallback(hdpaResults, FreeQueryResultCB, IntToPtr(ptd->cColumns));
|
|
|
|
hdpaResults = NULL;
|
|
}
|
|
|
|
if (!hdpaResults)
|
|
{
|
|
hdpaResults = DPA_Create(PAGE_SIZE);
|
|
TraceAssert(hdpaResults);
|
|
|
|
if (!hdpaResults)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Failed to allocate result DPA");
|
|
}
|
|
|
|
// Add the result we have to the result blob, the first
|
|
// two things we need are the class and the ADsPath of the
|
|
// object, then loop over the properties to generate the
|
|
// column data
|
|
|
|
pResult = (LPQUERYRESULT)LocalAlloc(LPTR, SIZEOF(QUERYRESULT)+(SIZEOF(COLUMNVALUE)*(ptd->cColumns-1)));
|
|
TraceAssert(pResult);
|
|
|
|
if (pResult)
|
|
{
|
|
// Get the ADsPath and ObjectClass of the object, these must remain UNICODE as
|
|
// they are used later for binding to the object. All other display information
|
|
// can be fixed up later.
|
|
|
|
pResult->iImage = -1;
|
|
|
|
// get the ADsPath. If the provider is GC: then replace with LDAP: so that
|
|
// when we interact with this object we are kept happy.
|
|
|
|
hr = pDsSearch->GetColumn(hSearch, c_szADsPath, &column);
|
|
FailGracefully(hr, "Failed to get the ADsPath column");
|
|
|
|
hr = StringFromSearchColumn(&column, &pResult->pPath);
|
|
pDsSearch->FreeColumn(&column);
|
|
|
|
Trace(TEXT("Object path: %s"), W2T(pResult->pPath));
|
|
|
|
if (SUCCEEDED(hr) &&
|
|
((pResult->pPath[0]== L'G') && (pResult->pPath[1] == L'C')))
|
|
{
|
|
TraceMsg("Replacing provider with LDAP:");
|
|
|
|
hr = LocalAllocStringLenW(&pszTempPath, lstrlenW(pResult->pPath)+2);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
StrCpyW(pszTempPath, c_szLDAP);
|
|
StrCatW(pszTempPath, pResult->pPath+3); // skip GC:
|
|
|
|
LocalFreeStringW(&pResult->pPath);
|
|
pResult->pPath = pszTempPath;
|
|
}
|
|
|
|
Trace(TEXT("New path is: %s"), W2T(pResult->pPath));
|
|
}
|
|
|
|
FailGracefully(hr, "Failed to get ADsPath from column");
|
|
|
|
// get the objectClass
|
|
|
|
hr = pDsSearch->GetColumn(hSearch, c_szObjectClass, &column);
|
|
FailGracefully(hr, "Failed to get the objectClass column");
|
|
|
|
hr = ObjectClassFromSearchColumn(&column, &pResult->pObjectClass);
|
|
pDsSearch->FreeColumn(&column);
|
|
FailGracefully(hr, "Failed to get object class from column");
|
|
|
|
// Now ensure that we have the icon cache, and then walk the list of columns
|
|
// getting the text that represents those.
|
|
|
|
if (SUCCEEDED(pdds->GetIconLocation(pResult->pObjectClass, DSGIF_GETDEFAULTICON, szBuffer, ARRAYSIZE(szBuffer), &resid)))
|
|
{
|
|
pResult->iImage = Shell_GetCachedImageIndex(W2T(szBuffer), resid, 0x0);
|
|
Trace(TEXT("Image index from shell is: %d"), pResult->iImage);
|
|
}
|
|
|
|
// is the class a container, mark this state into the result object
|
|
|
|
pResult->fIsContainer = pdds->IsClassContainer(pResult->pObjectClass, pResult->pPath, 0x0);
|
|
|
|
for (iColumn = 0 ; iColumn < ptd->cColumns ; iColumn++)
|
|
{
|
|
LPWSTR pProperty = ptd->aProperties[ptd->aColumnToPropertyMap[iColumn]];
|
|
TraceAssert(pProperty);
|
|
|
|
pResult->aColumn[iColumn].iPropertyType = PROPERTY_ISUNDEFINED; // empty column
|
|
|
|
hr = pDsSearch->GetColumn(hSearch, pProperty, &column);
|
|
if ((hr != E_ADS_COLUMN_NOT_SET) && FAILED(hr))
|
|
{
|
|
Trace(TEXT("Failed to get column %d with code %08x"), iColumn, hr);
|
|
hr = E_ADS_COLUMN_NOT_SET;
|
|
}
|
|
|
|
if (hr != E_ADS_COLUMN_NOT_SET)
|
|
{
|
|
LPCOLUMN pColumn = (LPCOLUMN)DSA_GetItemPtr(ptid->hdsaColumns, iColumn);
|
|
TraceAssert(pColumn);
|
|
|
|
switch (pColumn->iPropertyType)
|
|
{
|
|
case PROPERTY_ISUNKNOWN:
|
|
case PROPERTY_ISSTRING:
|
|
{
|
|
// we are treating the property as a string, therefore convert the search
|
|
// column to a string value and convert as required.
|
|
|
|
pResult->aColumn[iColumn].iPropertyType = PROPERTY_ISSTRING;
|
|
|
|
if (pColumn->fHasColumnHandler)
|
|
{
|
|
// we have the CLSID for a column handler, therefore lets CoCreate it
|
|
// and pass it to the ::GetText method.
|
|
|
|
if (!pColumn->pColumnHandler)
|
|
{
|
|
TraceGUID("Attempting to create IDsQueryColumnHandler from GUID: ", pColumn->clsidColumnHandler);
|
|
|
|
hr = CoCreateInstance(pColumn->clsidColumnHandler, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IDsQueryColumnHandler, (LPVOID*)&pColumn->pColumnHandler);
|
|
if (SUCCEEDED(hr))
|
|
hr = pColumn->pColumnHandler->Initialize(0x0, ptid->pServer, ptid->pUserName, ptid->pPassword);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
TraceMsg("Failed to CoCreate the column handler, marking the column as not having one");
|
|
pColumn->fHasColumnHandler = FALSE;
|
|
pColumn->pColumnHandler = NULL;
|
|
}
|
|
}
|
|
|
|
// if pColumnHandler != NULL, then call its ::GetText method, this is the string we should
|
|
// then place into the column.
|
|
|
|
if (pColumn->pColumnHandler)
|
|
{
|
|
pColumn->pColumnHandler->GetText(&column, szBuffer, ARRAYSIZE(szBuffer));
|
|
LocalAllocStringW2T(&pResult->aColumn[iColumn].pszText, szBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we were able to convert the column value to a string,
|
|
// then lets pass it to the column handler (if there is one
|
|
// to get the display string), or just copy this into the column
|
|
// structure (thunking accordingly).
|
|
|
|
if (SUCCEEDED(StringFromSearchColumn(&column, &pColumnData)))
|
|
{
|
|
LocalAllocStringW2T(&pResult->aColumn[iColumn].pszText, pColumnData);
|
|
LocalFreeStringW(&pColumnData);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PROPERTY_ISBOOL: // treat the BOOL as a number
|
|
case PROPERTY_ISNUMBER:
|
|
{
|
|
// its a number, therefore lets pick up the number value from the
|
|
// result and store that.
|
|
|
|
pResult->aColumn[iColumn].iPropertyType = PROPERTY_ISNUMBER;
|
|
pResult->aColumn[iColumn].iValue = column.pADsValues->Integer;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pDsSearch->FreeColumn(&column);
|
|
}
|
|
}
|
|
|
|
if (-1 == DPA_AppendPtr(hdpaResults, pResult))
|
|
{
|
|
FreeQueryResult(pResult, ptd->cColumns);
|
|
LocalFree((HLOCAL)pResult);
|
|
}
|
|
|
|
pResult = NULL;
|
|
}
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
exit_gracefully:
|
|
|
|
Trace(TEXT("cItems %d, (hdpaResults %08x (%d))"), cItems, hdpaResults, hdpaResults ? DPA_GetPtrCount(hdpaResults):0);
|
|
|
|
if (hdpaResults)
|
|
{
|
|
// As we send bunches of results to the fg thread check to see if we have a
|
|
// DPA with any pending items in it, if we do then lets ensure we post that
|
|
// off, if that succedes (the msg returns TRUE) then we are done, otherwise
|
|
// hdpaResults needs to be free'd
|
|
|
|
Trace(TEXT("Posting remaining results to fg thread (%d)"), DPA_GetPtrCount(hdpaResults));
|
|
|
|
if (SEND_VIEW_MESSAGE(ptid, DSQVM_ADDRESULTS, (LPARAM)hdpaResults))
|
|
hdpaResults = NULL;
|
|
}
|
|
|
|
if (!fStopQuery)
|
|
{
|
|
SEND_VIEW_MESSAGE(ptid, DSQVM_FINISHED, (cItems == MAX_RESULT));
|
|
}
|
|
|
|
if (pResult)
|
|
{
|
|
FreeQueryResult(pResult, ptd->cColumns);
|
|
LocalFree((HLOCAL)pResult);
|
|
}
|
|
|
|
if (hSearch && pDsSearch)
|
|
{
|
|
pDsSearch->CloseSearchHandle(hSearch);
|
|
}
|
|
|
|
LocalFreeStringW(&pQuery);
|
|
|
|
DoRelease(pDsSearch);
|
|
DoRelease(pdds);
|
|
|
|
QueryThread_FreePropertyList(ptd); // its void when we issue a new query
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread_BuildPropertyList
|
|
/ -----------------------------
|
|
/ Given the column DSA construct the property maps and the property
|
|
/ list we are going to query for. Internaly we always query for
|
|
/ ADsPath and objectClass, so walk the columns and work out
|
|
/ how many extra properties above this we have, then build an
|
|
/ array of property names containing the unique properties.
|
|
/
|
|
/ We also construct an index table that maps from a column index
|
|
/ to a property name.
|
|
/
|
|
/ In:
|
|
/ ptd -> thread information structurre
|
|
/
|
|
/ Out:
|
|
/ HRESULT
|
|
/----------------------------------------------------------------------------*/
|
|
HRESULT QueryThread_BuildPropertyList(LPTHREADDATA ptd)
|
|
{
|
|
HRESULT hr;
|
|
LPTHREADINITDATA ptid = ptd->ptid;
|
|
INT i, j;
|
|
USES_CONVERSION;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "QueryThread_BuildPropertyList");
|
|
|
|
// Walk the list of columns and compute the properties that are unique to this
|
|
// query and generate a table for them. First count the property table
|
|
// based on the columns DSA
|
|
|
|
ptd->cProperties = PROPERTYMAP_USER;
|
|
ptd->aProperties = NULL;
|
|
ptd->cColumns = DSA_GetItemCount(ptid->hdsaColumns);
|
|
ptd->aColumnToPropertyMap = NULL;
|
|
|
|
for (i = 0 ; i < DSA_GetItemCount(ptid->hdsaColumns); i++)
|
|
{
|
|
LPCOLUMN pColumn = (LPCOLUMN)DSA_GetItemPtr(ptid->hdsaColumns, i);
|
|
TraceAssert(pColumn);
|
|
|
|
if (StrCmpW(pColumn->pProperty, c_szADsPath) &&
|
|
StrCmpW(pColumn->pProperty, c_szObjectClass))
|
|
{
|
|
ptd->cProperties++;
|
|
}
|
|
}
|
|
|
|
Trace(TEXT("cProperties %d"), ptd->cProperties);
|
|
|
|
ptd->aProperties = (LPWSTR*)LocalAlloc(LPTR, SIZEOF(LPWSTR)*ptd->cProperties);
|
|
ptd->aColumnToPropertyMap = (INT*)LocalAlloc(LPTR, SIZEOF(INT)*ptd->cColumns);
|
|
|
|
if (!ptd->aProperties || !ptd->aColumnToPropertyMap)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Failed to allocate property array / display array");
|
|
|
|
ptd->aProperties[PROPERTYMAP_ADSPATH] = c_szADsPath;
|
|
ptd->aProperties[PROPERTYMAP_OBJECTCLASS] = c_szObjectClass;
|
|
|
|
for (j = PROPERTYMAP_USER, i = 0 ; i < ptd->cColumns; i++)
|
|
{
|
|
LPCOLUMN pColumn = (LPCOLUMN)DSA_GetItemPtr(ptid->hdsaColumns, i);
|
|
TraceAssert(pColumn);
|
|
|
|
if (!StrCmpW(pColumn->pProperty, c_szADsPath))
|
|
{
|
|
ptd->aColumnToPropertyMap[i] = PROPERTYMAP_ADSPATH;
|
|
}
|
|
else if (!StrCmpW(pColumn->pProperty, c_szObjectClass))
|
|
{
|
|
ptd->aColumnToPropertyMap[i] = PROPERTYMAP_OBJECTCLASS;
|
|
}
|
|
else
|
|
{
|
|
ptd->aProperties[j] = pColumn->pProperty;
|
|
ptd->aColumnToPropertyMap[i] = j++;
|
|
}
|
|
|
|
Trace(TEXT("Property: %s"), ptd->aProperties[ptd->aColumnToPropertyMap[i]]);
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
exit_gracefully:
|
|
|
|
if (FAILED(hr))
|
|
QueryThread_FreePropertyList(ptd);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ QueryThread_FreePropertyList
|
|
/ ----------------------------
|
|
/ Release a previously allocated property list assocaited with the
|
|
/ given thread.
|
|
/
|
|
/ In:
|
|
/ ptd -> thread information structurre
|
|
/
|
|
/ Out:
|
|
/ VOID
|
|
/----------------------------------------------------------------------------*/
|
|
VOID QueryThread_FreePropertyList(LPTHREADDATA ptd)
|
|
{
|
|
TraceEnter(TRACE_QUERYTHREAD, "QueryThread_FreePropertyList");
|
|
|
|
if (ptd->aProperties)
|
|
LocalFree((HLOCAL)ptd->aProperties);
|
|
if (ptd->aColumnToPropertyMap)
|
|
LocalFree((HLOCAL)ptd->aColumnToPropertyMap);
|
|
|
|
ptd->cProperties = 0;
|
|
ptd->aProperties = NULL;
|
|
ptd->cColumns = 0;
|
|
ptd->aColumnToPropertyMap = NULL;
|
|
|
|
TraceLeave();
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ CQueryThreadCH
|
|
/ --------------
|
|
/ Query thread column handler, this is a generic one used to convert
|
|
/ properties based on the CLSID we are instantiated with.
|
|
/----------------------------------------------------------------------------*/
|
|
|
|
class CQueryThreadCH : public IDsQueryColumnHandler
|
|
{
|
|
private:
|
|
LONG _cRef;
|
|
CLSID _clsid;
|
|
IADsPathname *_padp;
|
|
IDsDisplaySpecifier *_pdds;
|
|
|
|
DWORD _dwFlags;
|
|
LPWSTR _pszServer;
|
|
LPWSTR _pszUserName;
|
|
LPWSTR _pszPassword;
|
|
|
|
public:
|
|
CQueryThreadCH(REFCLSID rCLSID);
|
|
~CQueryThreadCH();
|
|
|
|
// *** IUnknown ***
|
|
STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObject);
|
|
STDMETHOD_(ULONG, AddRef)();
|
|
STDMETHOD_(ULONG, Release)();
|
|
|
|
// *** IDsQueryColumnHandler ***
|
|
STDMETHOD(Initialize)(THIS_ DWORD dwFlags, LPCWSTR pszServer, LPCWSTR pszUserName, LPCWSTR pszPassword);
|
|
STDMETHOD(GetText)(ADS_SEARCH_COLUMN* psc, LPWSTR pszBuffer, INT cchBuffer);
|
|
};
|
|
|
|
//
|
|
// constructor
|
|
//
|
|
|
|
CQueryThreadCH::CQueryThreadCH(REFCLSID rCLSID) :
|
|
_cRef(1),
|
|
_padp(NULL),
|
|
_pdds(NULL),
|
|
_clsid(rCLSID),
|
|
_dwFlags(0),
|
|
_pszServer(NULL),
|
|
_pszUserName(NULL),
|
|
_pszPassword(NULL)
|
|
{
|
|
TraceEnter(TRACE_QUERYTHREAD, "CQueryThreadCH::CQueryThreadCH");
|
|
TraceGUID("CLSID of property: ", rCLSID);
|
|
DllAddRef();
|
|
TraceLeave();
|
|
}
|
|
|
|
CQueryThreadCH::~CQueryThreadCH()
|
|
{
|
|
TraceEnter(TRACE_QUERYTHREAD, "CQueryThreadCH::~CQueryThreadCH");
|
|
|
|
DoRelease(_padp); // free the name cracker
|
|
DoRelease(_pdds);
|
|
|
|
LocalFreeStringW(&_pszServer);
|
|
LocalFreeStringW(&_pszUserName);
|
|
LocalFreeStringW(&_pszPassword);
|
|
|
|
DllRelease();
|
|
|
|
TraceLeave();
|
|
}
|
|
|
|
|
|
//
|
|
// Handler IUnknown interface
|
|
//
|
|
|
|
ULONG CQueryThreadCH::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
ULONG CQueryThreadCH::Release()
|
|
{
|
|
if (InterlockedDecrement(&_cRef))
|
|
return _cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CQueryThreadCH::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CQueryThreadCH, IDsQueryColumnHandler), // IID_IDsQueryColumnHandler
|
|
{0, 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
|
|
//
|
|
// Handle creating an instance of IDsFolderProperties for talking to WAB
|
|
//
|
|
|
|
STDAPI CQueryThreadCH_CreateInstance(IUnknown* punkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
|
|
{
|
|
CQueryThreadCH *pqtch = new CQueryThreadCH(*poi->pclsid);
|
|
if (!pqtch)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hr = pqtch->QueryInterface(IID_IUnknown, (void **)ppunk);
|
|
pqtch->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
/ IDsQueryColumnHandler
|
|
/----------------------------------------------------------------------------*/
|
|
|
|
STDMETHODIMP CQueryThreadCH::Initialize(THIS_ DWORD dwFlags, LPCWSTR pszServer, LPCWSTR pszUserName, LPCWSTR pszPassword)
|
|
{
|
|
TraceEnter(TRACE_QUERYTHREAD, "CQueryThread::Initialize");
|
|
|
|
LocalFreeStringW(&_pszServer);
|
|
LocalFreeStringW(&_pszUserName);
|
|
LocalFreeStringW(&_pszPassword);
|
|
|
|
// copy new parameters away
|
|
|
|
_dwFlags = dwFlags;
|
|
|
|
HRESULT hr = LocalAllocStringW(&_pszServer, pszServer);
|
|
if (SUCCEEDED(hr))
|
|
hr = LocalAllocStringW(&_pszUserName, pszUserName);
|
|
if (SUCCEEDED(hr))
|
|
hr = LocalAllocStringW(&_pszPassword, pszPassword);
|
|
|
|
DoRelease(_pdds) // discard previous IDisplaySpecifier object
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
STDMETHODIMP CQueryThreadCH::GetText(ADS_SEARCH_COLUMN* psc, LPWSTR pszBuffer, INT cchBuffer)
|
|
{
|
|
HRESULT hr;
|
|
LPWSTR pValue = NULL;
|
|
USES_CONVERSION;
|
|
|
|
TraceEnter(TRACE_QUERYTHREAD, "CQueryThreadCH::GetText");
|
|
|
|
if (!psc || !pszBuffer)
|
|
ExitGracefully(hr, E_UNEXPECTED, "Bad parameters passed to handler");
|
|
|
|
pszBuffer[0] = L'\0';
|
|
|
|
if (IsEqualCLSID(_clsid, CLSID_PublishedAtCH) || IsEqualCLSID(_clsid, CLSID_MachineOwnerCH))
|
|
{
|
|
BOOL fPrefix = IsEqualCLSID(_clsid, CLSID_PublishedAtCH);
|
|
LPCWSTR pszPath = psc->pADsValues[0].DNString;
|
|
TraceAssert(pszPath != NULL);
|
|
|
|
// convert the ADsPath into its canonical form which is easier for the user
|
|
// to understand, CoCreate IADsPathname now instead of each time we call
|
|
// PrettyifyADsPathname.
|
|
|
|
if (!_padp)
|
|
{
|
|
hr = CoCreateInstance(CLSID_Pathname, NULL, CLSCTX_INPROC_SERVER, IID_IADsPathname, (LPVOID*)&_padp);
|
|
FailGracefully(hr, "Failed to get IADsPathname interface");
|
|
}
|
|
|
|
if (FAILED(GetDisplayNameFromADsPath(pszPath, pszBuffer, cchBuffer, _padp, fPrefix)))
|
|
{
|
|
TraceMsg("Failed to get display name from path");
|
|
StrCpyNW(pszBuffer, pszPath, cchBuffer);
|
|
}
|
|
|
|
hr = S_OK;
|
|
}
|
|
else if (IsEqualCLSID(_clsid, CLSID_ObjectClassCH))
|
|
{
|
|
// get a string from the search column, and then look up the friendly name of the
|
|
// class from its display specifier
|
|
|
|
hr = ObjectClassFromSearchColumn(psc, &pValue);
|
|
FailGracefully(hr, "Failed to get object class from psc");
|
|
|
|
if (!_pdds)
|
|
{
|
|
DWORD dwFlags = 0;
|
|
|
|
hr = CoCreateInstance(CLSID_DsDisplaySpecifier, NULL, CLSCTX_INPROC_SERVER, IID_IDsDisplaySpecifier, (void **)&_pdds);
|
|
FailGracefully(hr, "Failed to get IDsDisplaySpecifier interface");
|
|
|
|
hr = _pdds->SetServer(_pszServer, _pszUserName, _pszPassword, DSSSF_DSAVAILABLE);
|
|
FailGracefully(hr, "Failed when setting server for display specifier object");
|
|
}
|
|
|
|
_pdds->GetFriendlyClassName(pValue, pszBuffer, cchBuffer);
|
|
}
|
|
else if (IsEqualCLSID(_clsid, CLSID_MachineOwnerCH))
|
|
{
|
|
// convert the DN of the user object into a string that we can display
|
|
}
|
|
else if (IsEqualCLSID(_clsid, CLSID_MachineRoleCH))
|
|
{
|
|
// convert the userAccountControl value into something we can display for the user
|
|
|
|
if (psc->dwADsType == ADSTYPE_INTEGER)
|
|
{
|
|
INT iType = psc->pADsValues->Integer; // pick out the type
|
|
|
|
if ((iType >= 4096) && (iType <= 8191))
|
|
{
|
|
TraceMsg("Returning WKS/SRV string");
|
|
LoadStringW(GLOBAL_HINSTANCE, IDS_WKSORSERVER, pszBuffer, cchBuffer);
|
|
}
|
|
else if (iType >= 8192)
|
|
{
|
|
TraceMsg("Returning DC string");
|
|
LoadStringW(GLOBAL_HINSTANCE, IDS_DC, pszBuffer, cchBuffer);
|
|
}
|
|
else
|
|
{
|
|
Trace(TEXT("Unknown type %x"), iType);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExitGracefully(hr, E_UNEXPECTED, "m_clsid specifies column type not supported");
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
exit_gracefully:
|
|
|
|
LocalFreeStringW(&pValue);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|