Windows Search Project

Well, a few months ago I searched MSDN for some API’s which could be interesting to me. I stumbled over the Windows Search API which I found really interesting since it gives you the possibility to programmatically search for files, in your emails or specific folders on your local drive.

 

 

Windows Search is a standard component of Windows Vista enabled by default and an add-in for Microsoft Windows XP which allows instant search capabilities for most common file and data types such as e-mail, contacts, calendar appointments, documents, photos, multimedia, and other formats extended by third-parties. These capabilities enable consumers and Information Workers to more efficiently find, manage, and organize the increasing amount of data common in home and enterprise environments. Read anything about it at MSDN.

Windows Desktop Search with Delphi

WDS with Delphi

 

 

 

 

 

 

Windows Search is built into Windows Vista and is available as a redistributable update to WDS 2.x, supporting the following operating systems:

  • 32-bit versions of Windows XP with Service Pack 2 (SP2)
  • All x64-based versions of Windows XP
  • Windows Server 2003 with Service Pack 1 (SP1) and later
  • All x64-based versions of Windows Server 2003

Systems running these operating systems must have Windows Search installed to run applications written for Windows Search. For more information and to download, refer to KB article 917013

The demo itself contains a class and a thread object inside of it due to the fact that if you for example want to re-index or indexing files or folders this could take a long time depending on the entrys inside of it. This would result into an GUI not responding to user activitys anymore until the index process had finished its work.

Doing it the OOP Way I’ve simply created a class, which lets you search whatever you want or e.g.  re-index or index new entrys. The search query makes use of the ISearchQueryHelper which is a COM interface that can be used to convert from Advanced Query Syntax (AQS) to a SQL query that can be passed to the OLE DB Provider for Windows Search.

AQS Syntax can be found on MSDN

The Thread

Type
unit SearchApiThread;

interface

uses { codegear units }
  SysUtils, ActiveX, ComObj, ShlObj, Dialogs,
  ComCtrls, Classes, Messages, Variants, Forms, SyncObjs,

  { imports }
  SearchAPILib_TLB,
  ADODB_TLB,

  { jwscl units }
  JwaWindows,
  JwsclUtils,
  JwsclExceptions;

const
  TS_THREAD_SEARCH_STARTED        = WM_USER + 1;     // OUT
  TS_THREAD_SEARCH_FINISHED       = WM_USER + 2;     // OUT
  TS_THREAD_SEARCH_ITEMS_COUNT    = WM_USER + 3;     // OUT
  TS_Thread_SEARCH_ITEMS_NEW_ENUM = WM_USER + 4;     // OUT

type
  // message is send to the mainthread when the thread executed a search
  TThreadSearchStartedMsg = record
    StartTime: integer;
  end;
  PThreadSearchStartedMsg = ^TThreadSearchStartedMsg;

  // message is send to the mainthread when the thread finished a search
  TThreadSearchFinishedMsg = record
    EndTime: integer;
  end;
  PThreadSearchFinishedMsg = ^TThreadSearchFinishedMsg;

  // message is send to the mainthread when the thread finished and we know how many items found
  TThreadSearchItemsCountMsg = record
    Items: integer;
  end;
  PThreadSearchItemsCountMsg = ^TThreadSearchItemsCountMsg;

  // enumeration started send the pointer back to the mainthread
  TThreadSearchItemsEnumMsg = record
    ItemName:  string;
    ItemCount: integer;
  end;
  PThreadSearchItemsEnumMsg = ^TThreadSearchItemsEnumMsg;

type
  PSearchThread = ^TSearchThread;

  TSearchThread = class(TJwThread)
  protected

    FConnectionStr: PWideChar;
    FSearchString:  PWideChar;

    { OleDBConnection inside a seperate thread to ensure no gui freezing}
    FAdoDBConnection: _Connection;
    FAdoRecordSet:    _Recordset;
  private

  public
    ThreadSearchStartedMsgPtr:    PThreadSearchStartedMsg;
    ThreadSearchFinishedMsgPtr:   PThreadSearchFinishedMsg;
    ThreadSearchItemsCountMsgPrt: PThreadSearchItemsCountMsg;
    ThreadSearchItemsEnumMsgPrt:  PThreadSearchItemsEnumMsg;

    constructor Create(const SearchPattern: PWideChar; pConnection: PWideChar;
      const FreeOnTerm: boolean);
    destructor Destroy; override;
    procedure Execute; override;
  end;

The Class

type
  TSearchApi = class(TObject)
  protected
    { SearchManager }
    FSearchManager:        ISearchManager;
    FSearchCatalogManager: ISearchCatalogManager;
    FQueryHelper:          ISearchQueryHelper;
    FSearchCrawlScopeManager: ISearchCrawlScopeManager;
    FSearchRoot:           ISearchRoot;

    {setter / getter }
    FConnectionString:  PWideChar;
    FConnectionTimeOut: cardinal;
    FSearchQuery, FSQL: PWideChar;

    FSearchCatalogStatus, FSearchCatalogStatusReason: string;

    FNumberOfItemsInCatalog:   integer;
    FNumberOfFilesToBeIndexed: integer;
    FLastindexedUrl:           PWideChar;
    FIndexerVersionString:     PWideChar;

    FContentLocale:      LCID;
    FQueryKeywordLocale: LCID;

    FSearchThread: TSearchThread;

    function GetSearchCatalogStatus: string;
    function GetSearchCatalogStatusReason: string;
    function GetNumberOfItemsInCatalog: integer;
    function GetNumberOfFilesToBeIndexed: integer;
    function GetIndexerVersionString: PWideChar;
    function GetLastIndexedURL: PWideChar;

  public
    constructor Create();
    destructor Destroy(); override;

    procedure ReIndexCatalog;
    procedure ReIndexViaPattern(MatchPattern: PWideChar);
    procedure ResetCatalog;
    procedure ReIndexRoot(Root: PWideChar);
    procedure RunQuery(SearchTxt: PWideChar);

    property SearchCatalogStatus: string Read GetSearchCatalogStatus;
    property SearchCatalogStatusReason: string
      Read GetSearchCatalogStatusReason;
    property NumberOfItemsInCatalog: integer
      Read GetNumberOfItemsInCatalog;
    property NumberOfFilesToBeIndexed: integer
      Read GetNumberOfFilesToBeIndexed;
    property IndexerVersionString: PWideChar
      Read GetIndexerVersionString;
    property LastIndexedURL: PWideChar Read GetLastIndexedURL;

  end;

Initialize is done via:

hr := CoInitializeEx(nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE);

We then can create the CoClasses for the SearchManager with:

      FSearchManager := CoCSearchManager.Create;
      FQueryHelper := nil;
      FSearchCatalogManager := nil;
      FSearchCrawlScopeManager := nil;

Next we try to get the Catalog “SystemIndex” for the SearchCatalogManager

(important to know that this is the only valid Catalog we can use!)

This can be done with the following line of code:

FSearchManager.GetCatalog('SystemIndex', FSearchCatalogManager);

We need two more interfaces the QueryHelper and the SystemScopeCrawlManager

The Queryhelper as mentioned before will translate an AQS Query into a valid SQL Statement automatically for us! While the SystemScopeCrawlManager e.g. lets us add new roots which then would be indexed.

FSearchCatalogManager.GetQueryHelper(FQueryHelper);
FSearchCatalogManager.GetCrawlScopeManager(FSearchCrawlScopeManager);

Now we set the connection with the help of the QueryHelper and tell it to fetch all results by setting it to -1

FQueryHelper.Get_ConnectionString(FConnectionString);
FQueryHelper.Set_QueryMaxResults(-1);

Another not unimportant step is that we make sure to set the right locales e.g.
if you have the English version with the Romanian MUI pack, the content locale is 2072 (ro) and the keyword locale is 1033 (en-us). After all necessary steps are done we then just make a call to our class which will handle the input query like:

SearchManager.RunQuery(PWideChar(edtSearch.Text));

Additional Information

As I have mentioned in the beginning of this article that I needed to import the AdoDB Type Library for this Demo. That was because of some strange behaviours using the build in Ado Components from Delphi 2009 Architect.
I can not verify here if other Versions of Delphi might be affected too. But there might be a serious Bug in the Adodb.pas at line: ?
There is a call made without resource protection (well not really without but at a point in the source where its already to late!) So whenever i tried to run an SQL Statement I got an unknown exception without any helpful description. So since I’ve imported the AdoDB Type Library any problems are gone.

Prerequisites

Some Delphi packages are necessary. They are not included and must therfore downloaded and installed (if necessary) separately. Please, also respect the license of these packages.