// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved // Sample application that demonstrates the use of the ATL OLEDB access to Windows Search // // Here are some interesting command lines to try // // wdsoledb /Select "System.ItemName, System.Size" /Sorting "System.Size ASC" /MaxResults 5 txt // // wdsoledb /Select "System.Size, System.ItemName" /Sorting "System.Size DESC" jpg // // wdsoledb /Select "System.ItemPathDisplay" wma // // wdsoledb /Select "System.ItemUrl" Content // // Also this sample demonstrates two other ways to retrieve results from Windows Search: // binding to C++ class (via ATL) and via IPropertyStore. They are invoked with /2 and /3 command line switches. // // NOTE: this sample requires ATL that is installed as part of Visual Studio 2008 #include "WSOleDB.h" #include "cmdline.h" using namespace std; // Helper functions void Usage(PCWSTR pszProgName) { wcout << L"Usage: " << endl << endl << pszProgName << endl; wcout << L" [/ContentLocale ]" << endl; wcout << L" [/ContentProperties \"\"]" << endl; wcout << L" [/KeywordLocale ]" << endl; wcout << L" [/MaxResults ]" << endl; wcout << L" [/Select \"\"]" << endl; wcout << L" [/Sorting ]" << endl; wcout << L" [/Syntax ]" << endl; wcout << L" [/TermExpansion ]" << endl; wcout << L" [/Where \"\"]" << endl; wcout << L" [query expression]" << endl; wcout << L"OR /2 for compile-time binding" << endl; wcout << L"OR /3 for binding to property store. This sample will only work on Windows 7." << endl; wcout << L" Note, /2 and /3 use hardcoded SQL strings" << endl; } // This helper function creates SQL string using query helper out of parameters HRESULT GetSQLStringFromParams(LCID lcidContentLocaleParam, PCWSTR pszContentPropertiesParam, LCID lcidKeywordLocaleParam, LONG nMaxResultsParam, PCWSTR pszSelectColumnsParam, PCWSTR pszSortingParam, SEARCH_QUERY_SYNTAX sqsSyntaxParam, SEARCH_TERM_EXPANSION steTermExpansionParam, PCWSTR pszWhereRestrictionsParam, PCWSTR pszExprParam, PWSTR *ppszSQL) { ISearchQueryHelper *pQueryHelper; // Create an instance of the search manager ISearchManager *pSearchManager; HRESULT hr = CoCreateInstance(__uuidof(CSearchManager), NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pSearchManager)); if (SUCCEEDED(hr)) { // Get the catalog manager from the search manager ISearchCatalogManager *pSearchCatalogManager; hr = pSearchManager->GetCatalog(L"SystemIndex", &pSearchCatalogManager); if (SUCCEEDED(hr)) { // Get the query helper from the catalog manager hr = pSearchCatalogManager->GetQueryHelper(&pQueryHelper); if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QueryContentLocale(lcidContentLocaleParam); if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QueryContentProperties(pszContentPropertiesParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QueryKeywordLocale(lcidKeywordLocaleParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QueryMaxResults(nMaxResultsParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QuerySelectColumns(pszSelectColumnsParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QuerySorting(pszSortingParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QuerySyntax(sqsSyntaxParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QueryTermExpansion(steTermExpansionParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->put_QueryWhereRestrictions(pszWhereRestrictionsParam); } if (SUCCEEDED(hr)) { hr = pQueryHelper->GenerateSQLFromUserQuery(pszExprParam, ppszSQL); } pQueryHelper->Release(); } pSearchCatalogManager->Release(); } pSearchManager->Release(); } return hr; } // This function retrieves parameters from command line arguments and converts them into query string HRESULT GetSQLStringFromCommandLine(int argc, WCHAR *argv[], PWSTR *ppszSQL) { *ppszSQL = NULL; HRESULT hr; if (argc < 2) { Usage(PathFindFileName(argv[0])); hr = E_INVALIDARG; } else { // these classes are used for parsing the command line parameters into // values used to initalize the query CContentLocaleParam ContentLocaleParam(GetUserDefaultLCID()); CContentPropertiesParam ContentPropertiesParam(L""); CKeywordLocaleParam KeywordLocaleParam(GetUserDefaultLCID()); CMaxResultsParam MaxResultsParam(-1); CSelectColumnsParam SelectColumnsParam(L"System.ItemName"); CSortingParam SortingParam(L""); CSyntaxParam SyntaxParam(L"AQS"); CTermExpansionParam TermExpansionParam(L"None"); CWhereRestrictionsParam WhereRestrictionsParam(L""); CExprParam ExprParam(L""); CParamBase* QueryHelperCallbackParams[] = { &ContentLocaleParam, &ContentPropertiesParam, &KeywordLocaleParam, &MaxResultsParam, &SelectColumnsParam, &SortingParam, &SyntaxParam, &TermExpansionParam, &WhereRestrictionsParam, &ExprParam }; hr = ParseParams(QueryHelperCallbackParams, ARRAYSIZE(QueryHelperCallbackParams), argc - 1, argv + 1); if (SUCCEEDED(hr)) { // Initialize the query helper with the information from the cmd line hr = GetSQLStringFromParams( ContentLocaleParam.Get(), ContentPropertiesParam.Get(), KeywordLocaleParam.Get(), MaxResultsParam.Get(), SelectColumnsParam.Get(), SortingParam.Get(), SyntaxParam.Get(), TermExpansionParam.Get(), WhereRestrictionsParam.Get(), ExprParam.Get(), ppszSQL); } } return hr; } void PrintDate(double date) { SYSTEMTIME sysTime; BOOL ok = VariantTimeToSystemTime(date, &sysTime); if (ok) { SYSTEMTIME localTime; ok = SystemTimeToTzSpecificLocalTime(NULL, &sysTime, &localTime); if (ok) { WCHAR szBuffer[100]; ok = GetDateFormat(LOCALE_USER_DEFAULT, 0, &localTime, NULL, szBuffer, ARRAYSIZE(szBuffer) / sizeof(WCHAR)); if (ok) { wcout << szBuffer; ok = GetTimeFormat(LOCALE_USER_DEFAULT, 0, &localTime, NULL, szBuffer, ARRAYSIZE(szBuffer) / sizeof(WCHAR)); if (ok) { wcout << L" " << szBuffer; } } } } if (!ok) { wcout << L"Could not print date " << date; } } // This helper function can print some propvariants and handles BSTR vectors void PrintPropVariant(REFPROPVARIANT variant) { if (variant.vt == (VT_ARRAY | VT_BSTR) && variant.parray->cDims == 1) { BSTR *pBStr; HRESULT hr = SafeArrayAccessData(variant.parray, reinterpret_cast(&pBStr)); if (SUCCEEDED(hr)) { for (unsigned int i = 0; i < variant.parray->rgsabound[0].cElements; i++) { if (i == 0) { wcout << L"["; } else { wcout << L"; "; } wcout << pBStr[i]; } wcout << L"]"; SafeArrayUnaccessData(variant.parray); } else { wcout << L"Could not print vector"; } } else { switch (variant.vt) { case VT_LPWSTR: wcout << variant.pwszVal; break; case VT_BSTR: wcout << variant.bstrVal; break; case VT_I1: wcout << variant.cVal; break; case VT_UI2: wcout << variant.uiVal; break; case VT_I2: wcout << variant.iVal; break; case VT_UI4: wcout << variant.ulVal; break; case VT_I4: wcout << variant.lVal; break; case VT_UI8: wcout << variant.uhVal.HighPart << variant.uhVal.LowPart; break; case VT_I8: wcout << variant.hVal.HighPart << variant.hVal.LowPart; break; case VT_DATE: PrintDate(variant.date); break; default: wcout << L"Unhandled variant type " << variant.vt; break; } } } // Start of the first sample which illustrates generic bindings. // Column type and values are resolved dynamically after next result is fetched. void BindToDynamicSample(int argc, WCHAR *argv[]) { PWSTR pszSQL; HRESULT hr = GetSQLStringFromCommandLine(argc, argv, &pszSQL); if (SUCCEEDED(hr)) { wcout << L"Generated query: " << pszSQL << endl; CDataSource cDataSource; hr = cDataSource.OpenFromInitializationString(L"provider=Search.CollatorDSO.1;EXTENDED PROPERTIES=\"Application=Windows\""); if (SUCCEEDED(hr)) { CSession cSession; hr = cSession.Open(cDataSource); if (SUCCEEDED(hr)) { CCommand cCommand; hr = cCommand.Open(cSession, pszSQL); if (SUCCEEDED(hr)) { for (hr = cCommand.MoveFirst(); S_OK == hr; hr = cCommand.MoveNext()) { for (DBORDINAL i = 1; i <= cCommand.GetColumnCount(); i++) { PCWSTR pszName = cCommand.GetColumnName(i); wcout << pszName << L" : "; DBSTATUS status; cCommand.GetStatus(i, &status); if (status == DBSTATUS_S_ISNULL) { wcout << L"NULL"; } else if (status == DBSTATUS_S_OK || status == DBSTATUS_S_TRUNCATED) { DBTYPE type; cCommand.GetColumnType(i, &type); switch (type) { case DBTYPE_VARIANT: PrintPropVariant(*(static_cast(cCommand.GetValue(i)))); break; case DBTYPE_WSTR: { DBLENGTH cbLen; cCommand.GetLength(i, &cbLen); WCHAR szBuffer[2048]; StringCchCopyN(szBuffer, ARRAYSIZE(szBuffer), static_cast(cCommand.GetValue(i)), cbLen / sizeof(WCHAR)); wcout << szBuffer; } break; case DBTYPE_I1: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_UI2: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_I2: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_UI4: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_I4: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_UI8: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_I8: wcout << *static_cast(cCommand.GetValue(i)); break; case DBTYPE_DATE: PrintDate(*static_cast(cCommand.GetValue(i))); break; default: wcout << L"Unhandled database type " << type; break; } } else { wcout << L"Error reading column " << i << L" (" << hr << L")"; } wcout << endl; } wcout << endl; } if (DB_S_ENDOFROWSET == hr) { hr = S_FALSE; // no rows } else { wcout << L"Query failed with error" << hr << endl; } cCommand.Close(); } else { wcout << L"Command (" << pszSQL << L") failed with error " << hr << endl; } cCommand.ReleaseCommand(); } } CoTaskMemFree(pszSQL); } } // End of generic bindings sample // Direct binding to C++ class. This sample uses hardcoded parameters for SQL string creation and binds to CMyAccessor class layout. class CMyAccessor { public: WCHAR _szItemUrl[2048]; __int64 _size; BEGIN_COLUMN_MAP(CMyAccessor) COLUMN_ENTRY(1, _szItemUrl) COLUMN_ENTRY(2, _size) END_COLUMN_MAP() }; void BindToAccessorSample() { PWSTR pszSQL; // This AQS will match all files named desktop.ini and return System.ItemPathDisplay and System.Size // which will be mapped to _szItemUrl and _size respectively HRESULT hr = GetSQLStringFromParams(1033, L"", 1033, -1, L"System.ItemPathDisplay, System.Size", L"", SEARCH_ADVANCED_QUERY_SYNTAX, SEARCH_TERM_NO_EXPANSION, L"", L"filename:desktop.ini", &pszSQL); if (SUCCEEDED(hr)) { wcout << L"Generated query: " << pszSQL << endl; CDataSource cDataSource; hr = cDataSource.OpenFromInitializationString(L"provider=Search.CollatorDSO.1;EXTENDED PROPERTIES=\"Application=Windows\""); if (SUCCEEDED(hr)) { CSession cSession; hr = cSession.Open(cDataSource); if (SUCCEEDED(hr)) { // cCommand is derived from CMyAccessor which has binding information in column map // This allows ATL to put data directly into apropriate class members. CCommand, CRowset> cCommand; hr = cCommand.Open(cSession, pszSQL); if (SUCCEEDED(hr)) { __int64 maxValue = 0; __int64 minValue = ULONG_MAX; for (hr = cCommand.MoveFirst(); S_OK == hr; hr = cCommand.MoveNext()) { wcout << cCommand._szItemUrl << L": " << cCommand._size << L" bytes" << endl; maxValue = max(maxValue, cCommand._size); minValue = min(minValue, cCommand._size); } wcout << L"Max:" << maxValue << L"Min:" << minValue << endl; cCommand.Close(); } cCommand.ReleaseCommand(); } } CoTaskMemFree(pszSQL); } } // End of direct binding sample. // Last sample doesn't use ATL. It demonstates how to retrieve and access IPropertyStore objects from IRowset // This sample requires Windows 7 to run successfully. // Resolve provider class id, create a provider, initialize it, create session and then return a command HRESULT GetWindowsSearchCommandObj(REFIID riid, void **ppv) { CLSID clsidWindowsSearch; *ppv = NULL; HRESULT hr = CLSIDFromProgID(L"Search.CollatorDSO.1", &clsidWindowsSearch); if (SUCCEEDED(hr)) { IDBInitialize *pdbi; hr = CoCreateInstance(clsidWindowsSearch, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pdbi)); if (SUCCEEDED(hr)) { hr = pdbi->Initialize(); if (SUCCEEDED(hr)) { IDBCreateSession *pdbcs; hr = pdbi->QueryInterface(IID_PPV_ARGS(&pdbcs)); if (SUCCEEDED(hr)) { IDBCreateCommand *pdbcc; hr = pdbcs->CreateSession(0, IID_IDBCreateCommand, reinterpret_cast(&pdbcc)); if (SUCCEEDED(hr)) { ICommand *pcmd; hr = pdbcc->CreateCommand(0, IID_ICommand, reinterpret_cast(&pcmd)); if (SUCCEEDED(hr)) { hr = pcmd->QueryInterface(riid, ppv); pcmd->Release(); } pdbcc->Release(); } pdbcs->Release(); } } pdbi->Release(); } } return hr; } // Create ICommandText object and execute provided SQL text resulting in IRowset object HRESULT ExecuteQuery(PCWSTR pwszSql, REFIID riid, void **ppv) { ICommandText *pcmdText; *ppv = NULL; HRESULT hr = GetWindowsSearchCommandObj(IID_PPV_ARGS(&pcmdText)); if (SUCCEEDED(hr)) { hr = pcmdText->SetCommandText(DBGUID_DEFAULT, pwszSql); if (SUCCEEDED(hr)) { DBROWCOUNT cRows; hr= pcmdText->Execute(NULL, riid, NULL, &cRows, reinterpret_cast(ppv)); } pcmdText->Release(); } return hr; } // Helper function used to strip off non-printable characters void _StripCharacters(PWSTR pszText, PCWSTR pszRemove) { PWSTR pszSource = pszText; PWSTR pszDest = pszSource; while (*pszSource) { // Skip copying characters found in pszRemove if (!StrChr(pszRemove, *pszSource)) { *pszDest = *pszSource; pszDest++; } pszSource++; } *pszDest = 0; // NULL terminate } // Helper function to retrieve the value from property store and print it HRESULT PrintProperty(IPropertyStore *pps, REFPROPERTYKEY pkey) { IPropertyDescription *ppd; HRESULT hr = PSGetPropertyDescription(pkey, IID_PPV_ARGS(&ppd)); if (SUCCEEDED(hr)) { WCHAR *pszName; hr = ppd->GetCanonicalName(&pszName); if (SUCCEEDED(hr)) { wcout << L"Aggregated " << pszName << L" : "; PROPVARIANT propvar; hr = pps->GetValue(pkey, &propvar); if (SUCCEEDED(hr)) { WCHAR *pszValue; hr = ppd->FormatForDisplay(propvar, PDFF_NOAUTOREADINGORDER, &pszValue); if (SUCCEEDED(hr)) { // Strip remaining order reading marks from the string: // LRM RLM LRE RLE PDF LRO RLO _StripCharacters(pszValue, L"\x200e\x200f\x202a\x202b\x202c\x202d\x202e"); wcout << pszValue << endl; CoTaskMemFree(pszValue); } PropVariantClear(&propvar); } CoTaskMemFree(pszName); } ppd->Release(); } return hr; } // Following query aggregates sizes and last modified dates of all indexed items under c:\users // Grouping on System.Null will result in a single group containing all SELECT-ed results. // Note that SELECT only fetches System.Search.EntryID which is the fastest property to retrieve. // DATERANGE is a new aggregate, introduced in Windows 7. WCHAR c_szAggregateSizeInUsers[] = L"GROUP ON System.Null AGGREGATE SUM(System.Size), DATERANGE(System.DateModified) OVER (" L"SELECT System.Search.EntryID from SystemIndex WHERE Scope='file:c:/users'" L")"; // Execute the query, fetch first row and get a property store out of it, then print relevant properties void BindToPropertyStoreSample() { wcout << L"Query text: " << c_szAggregateSizeInUsers << endl; IRowset *prs; HRESULT hr = ExecuteQuery(c_szAggregateSizeInUsers, IID_PPV_ARGS(&prs)); if (SUCCEEDED(hr)) { // The query above should only return a single result, thus only one HROW is enough HROW hRow; HROW *pgrHRows = &hRow; DBCOUNTITEM cRowsReturned; hr = prs->GetNextRows(0, 0, 1, &cRowsReturned, &pgrHRows); if (SUCCEEDED(hr)) { if (cRowsReturned == 0) { wcout << L"Query returned zero results, check that the scope is indexed" << endl; } else { // Retrieve IPropertyStore and dump information from it IGetRow *pGetRow; hr = prs->QueryInterface(IID_PPV_ARGS(&pGetRow)); if (SUCCEEDED(hr)) { IPropertyStore *pps; hr = pGetRow->GetRowFromHROW(NULL, hRow, IID_IPropertyStore, reinterpret_cast(&pps)); if (SUCCEEDED(hr)) { hr = PrintProperty(pps, PKEY_Size); if (SUCCEEDED(hr)) { hr = PrintProperty(pps, PKEY_DateModified); } pps->Release(); } pGetRow->Release(); } } } prs->Release(); } } // End of IPropertyStore sample void wmain(int argc, wchar_t *argv[]) { HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); if (SUCCEEDED(hr)) { try { // Handle /2 and /3 as special cases, otherwise do command line parsing if (argc > 1 && argv[1][0] == L'/' && argv[1][1] == L'2') { BindToAccessorSample(); } else if (argc > 1 && argv[1][0] == L'/' && argv[1][1] == L'3') { BindToPropertyStoreSample(); } else { BindToDynamicSample(argc, argv); } } catch (CAtlException &e) { wcout << L"Caught ATL exception " << e.m_hr << endl; hr = e; } CoUninitialize(); } }