Title: Add MS Index Server Search capabilities to your Delphi app
Question: Microsoft Index Server is a standard component of Windows NT/2000 products. Windows NT 4 Server comes with Index Server 2, and 2000 Server and Professional come with Index Server 3. According to Microsoft documentation "Indexing Service is a base service of Microsoft Windows NT/Windows 2000 that extracts content from files and constructs an indexed catalog to facilitate efficient and rapid searching". Primarily it is used as a Web search engine.
In this article I will focus on how to access the MS Index Server from Delphi app in order to perform administrative tasks and execute search queries. For detailed information on MS Index
Server including query language refer to MSDN SDK Documentation for Indexing Service.
Answer:
First things first
Before you go ahead with researching Index Server and playing with source code from this article you need to import two type libraries.
First, ixsso.dll, will provide you with Query and Utility objects. Second, ciodm.dll, will provide AdminIndexServer, CatAdm, and ScopeAdm objects. (Make sure the 'Generate Component Wrapper' box is checked in Import Type Library dialogue.)
The following is a quote from MS documentation:
Indexing Service Query Automation Objects
(Object name - Description)
Query - Manages the query definition and result-set creation and navigation.
Utility - Helpful utilities for managing the queries and result sets.
Indexing Service Administration Automation Objects
AdminIndexServer - Manages Indexing Service. Allows access to all the functionality provided by the Microsoft Management Console (MMC) snap-in.
CatAdm - Manages the collection of scopes for a catalog.
ScopeAdm - Manages an individual scope.
Get in touch with Index Server
Now with your project Index Server automation objects (and interfaces) aware here is the sample form's OnCreate event (see source code).
procedure TForm1.FormCreate(Sender: TObject);
var
IndexServerAdmin: TAdminIndexServer; //Index server administration automation object
CatAdm : TCatAdm; //Catalog administration automation object
begin
//Both IndexQuery and IndexUtil objects are declared in main unit
IndexQuery := TCissoQuery.Create(nil);
IndexUtil := TCissoUtil.Create(nil);
//Let's display a list of catalogs available on local index server
IndexServerAdmin := TAdminIndexServer.Create(nil);
//Check if the server is running.
CheckBox1.Checked := IndexServerAdmin.IsRunning;
if IndexServerAdmin.FindFirstCatalog then
begin
CatAdm := TCatAdm.Create(nil);
CatAdm.ConnectTo(InsexServerAdmin.GetCatalog() as ICatAdm);
//Add catalog name to the ListBox.
ListBox1.Items.Add(CatAdm.CatalogName);
CatAdm.Free;
while InsexServerAdmin.FindNextCatalog do
begin
CatAdm := TCatAdm.Create(nil);
CatAdm.ConnectTo(InsexServerAdmin.GetCatalog() as ICatAdm);
//Add catalog name to the ListBox.
ListBox1.Items.Add(CatAdm.CatalogName);
CatAdm.Free;
end;
end;
InsexServerAdmin.Free;
end;
IndexServerAdmin.IsRunning here is used just to set the CheckBox state.
InsexServerAdmin.GetCatalog() returns ICatAdm type, and CatAdm.ConnectTo is used to connect CatAdm automation object to index server. Index server automation object allows you to check the server status (IsRunning, IsPaused methods) as well as change status (Start, Stop, Pause methods), catalogs enumeration (FindFIrstCatalog, FindNextCatalog methods - see sample code), and retrieve catalog (GetCatalog, GetCatalogByName).
Admin Index Server resources
Here is the code on the ListBox1 (list of catalogs) OnClick method.
As soon as catalog is selected it's location (location of the catalog file) is displayed in the Label7.
Also scopes are listed in a ListBox2. Scope in fact is a resource (folder) all contents of which are indexed.
procedure TForm1.ListBox1Click(Sender: TObject);
var
IndexServerAdmin: TAdminIndexServer;
CatAdm : TCatAdm;
ScopeAdm: TScopeAdm;
begin
{This is a Search button. Will be used later.
It is disabled until catalog is selected from a ListBox1.}
Button1.Enabled := true;
IndexServerAdmin := TAdminIndexServer.Create(nil);
CatAdm := TCatAdm.Create(nil);
CatAdm.ConnectTo(IndexServerAdmin.GetCatalogByName( ListBox1.Items[ListBox1.ItemIndex] ) as ICatAdm);
Label7.Caption := 'Catalog location: ' + CatAdm.CatalogLocation ;
//Enumerate scopes
ListBox2.Clear ;
if CatAdm.FindFirstScope then
begin
ScopeAdm := TScopeAdm.Create(nil);
ScopeAdm.ConnectTo(CatAdm.GetScope() as IScopeAdm);
ListBox2.Items.add(ScopeAdm.Path);
ScopeAdm.free;
while CatAdm.FindNextScope do
begin
ScopeAdm := TScopeAdm.Create(nil);
ScopeAdm.ConnectTo(CatAdm.GetScope() as IScopeAdm);
ListBox2.Items.add(ScopeAdm.Path);
ScopeAdm.free;
end;
end;
CatAdm.Free;
IndexServerAdmin.Free;
end;
For each of catalogs and scopes you can check and set the status. Also you can add new catalog to server, as well as add new scope to catalog, which in turn is a collection of scopes.
A couple important notes here. Administrative tasks (like adding a catalog) should be applied to the index server when it is stopped. As soon as new catalog or scope added and server is index server started again, give some time for the first run of contents indexing. All further content updates would be followed with incremental index updates (at list it works this way on NTFS).
Executing the search query
Here is the search button Button1 OnClick event code. The important point here is that it does not use ADO provider for Index Server thus excluding a lot of overhead work. Query results though are obtained as TADODataSet object which you can use for further search results processing.
In this sample code I used DBGrid1:TDBGrid and DataSource1:TDataSource to display search results.
procedure TForm1.Button1Click(Sender: TObject);
var
i, ii: Integer;
ds: TADODataSet;
rs: _Recordset; //Defined in ADOdb unit
begin
IndexQuery.Reset ;
//Enter query text or just a word in Edit1
IndexQuery.Query := '$contents "' + Edit1.Text + '"';
//List fields you want the query to return
IndexQuery.Columns := 'DocTitle,Path,Write,Rank';
//Define query result sort order
IndexQuery.SortBy := 'Rank [d]';
IndexQuery.MaxRecords := 1000;
//Catalog selcted in Listbox1
IndexQuery.Catalog := ListBox1.Items[ListBox1.ItemIndex];
Memo1.Lines.Clear;
//Output simple status report
Memo1.Lines.Add( 'Searching for ' + IndexQuery.Query + ' in "' + IndexQuery.Catalog + '"');
try
ds := TADODataSet.Create(nil);
//Retrieving query results - the trickiest part.
ds.RecordSet := (IndexQuery.CreateRecordset('nonsequential') as _Recordset);
ii := ds.Recordset.RecordCount - 1;
memo1.Lines.Add('num of docs: ' + IntToStr(ii));
except
memo1.Lines.Add('did not work on "' + IndexQuery.Catalog + '"');
end;
//Assign Recordset object to ADODataSet's RecordSet property.
ADODataSet1.RecordSet := ds.Recordset;
ds.Close;
ds.Free;
end;
Enjoy!