2025-11-28 00:35:46 +09:00

454 lines
17 KiB
C++

// 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
//
// The application provides three ways to specify the files to reindex.
//
// Supported commands:
// filetype Reindexes all URLs that match the given file extensions
// mimetype Reindexes all URLs that match the given mimetypes
// where Reindexes all URLs that match a given WHERE clause
//
// The filetype test shows the usage of the ReindexMatchingUrls function. The other two options queries
// the indexer directly for the matching urls, then sends a message to add and delete the files. The boiler-plate
// code in CmdLineBase.h/.cpp is used to simplify the tasks of processing options, printing usage information etc.
// but this is not required to use the IShellLibrary API.
//
// Usage information for each command can be obtained via the '-?' option (-h and -help are also supported).
#include <stdio.h>
#include <windows.h>
#include <searchapi.h>
#include <iostream>
#include <stdlib.h>
#include <atlbase.h>
#include <cmdlinebase.h>
#include <atldbcli.h>
#include <strsafe.h>
#include <WinInet.h>
class CReindexWhereClauseCommand;
class CReindexMatchingMimeTypeCommand;
class CReindexMatchingWhereClauseCommand;
class CReindexMatchingFileTypeCommand;
#define CONSUME_NEXT_ARG(ppszArgs, cArgs) ((cArgs)--, ((ppszArgs)++)[0])
int wmain(int argc, wchar_t *argv[])
{
// initialize COM before doing anything else, since the IShellLibrary API depends on COM
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
CMetaCommand::PFNCREATECOMMAND rgMainCmds[] =
{
CMetaCommand::Create<CReindexMatchingFileTypeCommand>,
CMetaCommand::Create<CReindexMatchingMimeTypeCommand>,
CMetaCommand::Create<CReindexMatchingWhereClauseCommand>,
};
{
PCWSTR pszExeName = PathFindFileNameW(CONSUME_NEXT_ARG(argv, argc));
CMetaCommand main(pszExeName, L"Reindexes the files that match the requirements specified. \nreindex <command> -? will give you the usage for each command", rgMainCmds, ARRAYSIZE(rgMainCmds));
main.Execute(const_cast<PCWSTR *>(argv), argc);
}
CoUninitialize();
}
return 0;
}
// CReindexMatchingFileTypeCommand - Reindexes the URLs matching the given filetypes
//
// This class handles the filetype command. It uses the ReindexMatchingURLs command to reindex the
// filetypes specified.
class CReindexMatchingFileTypeCommand :
public CCmdBase
{
public:
CReindexMatchingFileTypeCommand(PCWSTR pszName = L"filetype", PCWSTR pszDescription = L"Reindexes all URLs that match the given file extensions") :
CCmdBase(pszName, pszDescription, L"<.FILETYPE1> [<.FILETYPE2> ...] (EX. reindex filetype .foo .bar)")
{
}
// Processes the filetypes that we are reindexing.
HRESULT v_ProcessArguments(PCWSTR* ppszArgs, int cArgs)
{
HRESULT hr = cArgs ? S_OK : E_FAIL;
if (FAILED(hr))
{
ParseError(L"Invalid number of arguments.");
}
else
{
// We are going to create a copy of ppszArgs to make an array of filetypes.
m_ppszFileTypes = (PWSTR*) CoTaskMemAlloc(sizeof(PWSTR*) * cArgs);
hr = m_ppszFileTypes ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
m_cArgs = cArgs;
for (int i = 0; (i < m_cArgs) && SUCCEEDED(hr); i++)
{
PCWSTR pszArg = CONSUME_NEXT_ARG(ppszArgs, cArgs);
hr = pszArg[0] == L'.' ? S_OK : E_FAIL;
if (FAILED(hr))
{
ParseError(L"'%s' is not a valid filetype.", pszArg);
}
else
{
hr = SHStrDup(pszArg, &(m_ppszFileTypes[i]));
}
}
if (FAILED(hr))
{
FreeAll();
}
}
}
return hr;
}
HRESULT v_ExecuteCommand()
{
ISearchManager *pSearchManager;
HRESULT hr = CoCreateInstance(__uuidof(CSearchManager), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pSearchManager));
if (SUCCEEDED(hr))
{
ISearchCatalogManager *pCatalogManager;
hr = pSearchManager->GetCatalog(L"SystemIndex", &pCatalogManager);
if (SUCCEEDED(hr))
{
for (int i = 0; (i < m_cArgs) && SUCCEEDED(hr); i++)
{
WCHAR szPattern[MAX_PATH];
// We are creating a pattern that looks like *.FILETYPE and reindexing urls that match this pattern.
hr = StringCchPrintf(szPattern, ARRAYSIZE(szPattern), L"*%s", szPattern);
if (SUCCEEDED(hr))
{
hr = pCatalogManager->ReindexMatchingURLs(szPattern);
}
}
pCatalogManager->Release();
}
pSearchManager->Release();
}
if (SUCCEEDED(hr))
{
Output(L"Succeeded!");
}
FreeAll();
return S_OK;
}
private:
PWSTR *m_ppszFileTypes;
int m_cArgs;
// This function frees the saved arguments in m_ppszFileTypes
void FreeAll()
{
for (int i = 0; i < m_cArgs; i++)
{
CoTaskMemFree(m_ppszFileTypes[i]);
}
CoTaskMemFree(m_ppszFileTypes);
}
};
// CReindexWhereClauseCommand - The base class that uses a WHERE clause to find the URLs that need
// reindexing.
//
// This is a base class for the mimetype and where subcommands. The argument processing is done in
// the subclasses during which a WHERE clause is created.
class CReindexWhereClauseCommand :
public CCmdBase
{
public:
CReindexWhereClauseCommand(PCWSTR pszName, PCWSTR pszDescription, PCWSTR pszUsage) :
CCmdBase(pszName, pszDescription, pszUsage)
{
}
virtual HRESULT v_ProcessArguments(PCWSTR* ppszArgs, int cArgs) = 0;
// The m_szWhereClause is already set in v_ProcessArguments
HRESULT v_ExecuteCommand()
{
UINT uiItemsReindexed;
HRESULT hr = ReindexMatchingUrls(m_szWhereClause, &uiItemsReindexed);
if (SUCCEEDED(hr))
{
Output(L"%d items reindexed", uiItemsReindexed);
}
return hr;
}
protected:
// v_ProcessArguments will fill this
WCHAR m_szWhereClause[1024];
private:
// class which is returned for each result in the query
class CItem
{
private:
WCHAR _szUrl[INTERNET_MAX_URL_LENGTH];
public:
PWSTR GetUrl() { return _szUrl; };
BEGIN_COLUMN_MAP(CItem)
COLUMN_ENTRY(1, _szUrl)
END_COLUMN_MAP()
};
HRESULT GetItemsChangedSink(ISearchPersistentItemsChangedSink **ppspics)
{
*ppspics = NULL;
ISearchManager *pSearchManager;
HRESULT hr = CoCreateInstance(__uuidof(CSearchManager), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pSearchManager));
if (SUCCEEDED(hr))
{
// Get the catalog manager from the search manager
ISearchCatalogManager *pCatalogManager;
hr = pSearchManager->GetCatalog(L"SystemIndex", &pCatalogManager);
if (SUCCEEDED(hr))
{
// get the notification change sink so we can send notifications
hr = pCatalogManager->GetPersistentItemsChangedSink(ppspics);
pCatalogManager->Release();
}
pSearchManager->Release();
}
return hr;
}
// This function will find the name of the original email URL if the given url is an argument
// returns S_FALSE if the URL isn't an argument
// returns S_OK if the URL is an attacment, pszOrigEmailUrl is filled with the original email URL
HRESULT IfAtachmentmentGetOrigEmailURL(PCWSTR pszUrl, int cchUrl, PWSTR pszOrigEmailUrl, int cchOrigEmailUrl)
{
HRESULT hr = S_FALSE;
// we are looking for '/at='
int cchSearchString = 4;
// start cchSearchString away from the end
WCHAR *ptrString = ((WCHAR*)pszUrl) + cchUrl - cchSearchString;
for (int i = cchUrl - cchSearchString; i >= 0; i--)
{
// email attachments end with /at=filename
if (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, ptrString, cchSearchString, L"/at=", cchSearchString) == CSTR_EQUAL)
{
// we found it, we should copy everything until the '/' to pszOrigEmailUrl
hr = StringCchCopyN(pszOrigEmailUrl, cchOrigEmailUrl, pszUrl, i);
if (SUCCEEDED(hr))
{
hr = S_OK;
}
break;
}
ptrString--;
}
return hr;
}
HRESULT ReindexUrl(PCWSTR pszUrl, ISearchPersistentItemsChangedSink *pItemsNotify)
{
// generate a DELETE notification causing the URL to go away
SEARCH_ITEM_PERSISTENT_CHANGE changes[2];
changes[0].Change = SEARCH_CHANGE_DELETE;
changes[0].Priority = SEARCH_NORMAL_PRIORITY;
changes[0].URL = NULL;
changes[0].OldURL = (PWSTR)pszUrl;
changes[1].Change = SEARCH_CHANGE_ADD;
changes[1].Priority = SEARCH_NORMAL_PRIORITY;
changes[1].URL = (PWSTR)pszUrl;
changes[1].OldURL = NULL;
// tell the index that the items need to be updated with add/delete
// change events (OnItemsChanged is responsible for freeing URLs)
HRESULT hrChanges[ARRAYSIZE(changes)];
HRESULT hr = pItemsNotify->OnItemsChanged(ARRAYSIZE(changes), changes, hrChanges);
if (FAILED(hr))
{
Output(L"OnItemsChanged(%s) failed with 0x%x\n", pszUrl, hr);
// dump the individual hr's
Output(L"\tDELETE hr = 0x%x\n", hrChanges[0]);
Output(L"\tNEW hr = 0x%x\n\n", hrChanges[1]);
}
return hr;
}
HRESULT ReindexMatchingUrls(PCWSTR pszWhere, UINT *puiItemsReindexed)
{
*puiItemsReindexed = 0;
WCHAR szQuery[2048];
HRESULT hr = StringCchPrintf(szQuery, ARRAYSIZE(szQuery), L"select System.ItemUrl from SystemIndex where %s", pszWhere);
if (SUCCEEDED(hr))
{
ISearchPersistentItemsChangedSink *pItemsNotify;
hr = GetItemsChangedSink(&pItemsNotify);
if (SUCCEEDED(hr))
{
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<CAccessor<CItem>, CRowset> cItems;
// execute the SQL, get back the recordset
hr = cItems.Open(cSession, szQuery);
if (SUCCEEDED(hr))
{
for (hr = cItems.MoveFirst(); hr == S_OK; hr = cItems.MoveNext())
{
// get the url from the current item
PWSTR pszUrl = cItems.GetUrl();
int cchUrl = lstrlen(pszUrl);
if (0 < cchUrl)
{
// we only need to compare the first five characters
int cchUrlCmp = cchUrl <= 5 ? cchUrl : 5;
// mapi urls need to be massaged to deal with attachments
if (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, pszUrl, cchUrlCmp, L"mapi:", 5) == CSTR_EQUAL)
{
WCHAR pszOrigEmailUrl[INTERNET_MAX_URL_LENGTH];
// If it is an attachment URL then it needs to be truncated to get the content of the email reindexed
HRESULT hrReindex = IfAtachmentmentGetOrigEmailURL(pszUrl, cchUrl, pszOrigEmailUrl, ARRAYSIZE(pszOrigEmailUrl));
// if the attachement was a url, reindex the content as well
if (S_OK == hrReindex)
{
if (SUCCEEDED(ReindexUrl(pszOrigEmailUrl, pItemsNotify)))
{
Output(L"Reindexed - %s\n\n", pszOrigEmailUrl);
(*puiItemsReindexed)++;
}
else
{
Output(L"Failed to reindex - %s\n\n", pszUrl);
}
}
}
}
if (SUCCEEDED(ReindexUrl(pszUrl, pItemsNotify)))
{
Output(L"Reindexing - %s\n\n", pszUrl);
(*puiItemsReindexed)++;
}
else
{
Output(L"Failed to reindex - %s\n\n", pszUrl);
}
}
// if we hit the end of the result set it's a normal S_OK
if (hr == DB_S_ENDOFROWSET)
hr = S_OK;
}
cSession.Close();
}
cDataSource.Close();
}
pItemsNotify->Release();
}
}
return hr;
}
};
// CReindexMatchingMimeTypeCommand - This is the class the processes the mimetype command.
//
// The v_processArguments builds up the where clause using the mimetypes specified. The base class (CReindexWhereClauseCommand)
// processes the where clause and reindexes the matching URLs.
class CReindexMatchingMimeTypeCommand :
public CReindexWhereClauseCommand
{
public:
CReindexMatchingMimeTypeCommand(PCWSTR pszName = L"mimetype", PCWSTR pszDescription = L"Reindexes all URLs that match the given mimetypes") :
CReindexWhereClauseCommand(pszName, pszDescription, L"<MIMETYPE1> [<MIMETYPE2> ...] (EX. reindex mimetype image/jpeg)")
{
}
HRESULT v_ProcessArguments(PCWSTR* ppszArgs, int cArgs)
{
// query against System.MimeType field
HRESULT hr = cArgs ? S_OK : E_FAIL;
if (FAILED(hr))
{
ParseError(L"Invalid number of arguments");
}
else
{
hr = StringCchCopy(m_szWhereClause, ARRAYSIZE(m_szWhereClause), L"contains(System.MimeType,'");
if (SUCCEEDED(hr))
{
// add on clauses to the contains statement
hr = StringCchCat(m_szWhereClause, ARRAYSIZE(m_szWhereClause), CONSUME_NEXT_ARG(ppszArgs, cArgs));
if (SUCCEEDED(hr))
{
while (cArgs)
{
hr = StringCchCat(m_szWhereClause, ARRAYSIZE(m_szWhereClause), L" OR ");
if (SUCCEEDED(hr))
{
hr = StringCchCat(m_szWhereClause, ARRAYSIZE(m_szWhereClause), CONSUME_NEXT_ARG(ppszArgs, cArgs));
}
}
}
if (SUCCEEDED(hr))
{
hr = StringCchCat(m_szWhereClause, ARRAYSIZE(m_szWhereClause), L"')");
}
}
}
return hr;
}
};
// CReindexMatchingWhereClauseCommand - This is the class the processes the where command.
//
// The v_processArguments builds up the where clause
class CReindexMatchingWhereClauseCommand :
public CReindexWhereClauseCommand
{
public:
CReindexMatchingWhereClauseCommand(PCWSTR pszName = L"where", PCWSTR pszDescription = L"Reindexes all URLs that match a given WHERE clause") :
CReindexWhereClauseCommand(pszName, pszDescription, L"<WHERE_CLAUSE> (EX. reindex where System.ItemNameDisplay = 'test.txt')")
{
}
HRESULT v_ProcessArguments(PCWSTR* ppszArgs, int cArgs)
{
// just copy the rest of ppszArgs into one string.
HRESULT hr = cArgs ? S_OK : E_FAIL;
if (FAILED(hr))
{
ParseError(L"Invalid number of arguments.");
}
else
{
StringCchCopy(m_szWhereClause, ARRAYSIZE(m_szWhereClause), CONSUME_NEXT_ARG(ppszArgs, cArgs));
while (cArgs && SUCCEEDED(hr))
{
hr = StringCchCat(m_szWhereClause, ARRAYSIZE(m_szWhereClause), L" ");
if (SUCCEEDED(hr))
{
hr = StringCchCat(m_szWhereClause, ARRAYSIZE(m_szWhereClause), CONSUME_NEXT_ARG(ppszArgs, cArgs));
}
}
}
return hr;
}
};