Platform SDK: Active Directory, ADSI, and Directory Services

Searching with IDirectorySearch

The IDirectorySearch interface provides a very high level and low overhead interface for querying data of a directory or a global catalog. The IDirectorySearch COM interface can only be used with a vtable, and thus, is not available to Automation-based development environments (such as Visual Basic, VBScript, and so on).

To perform a search

  1. Bind to an object in the directory.
  2. Call QueryInterface to get the IDirectorySearch pointer.
  3. Execute the search using the IDirectorySearch pointer. Call the IDirectorySearch::ExecuteSearch method, and pass a search filter, the requested attribute names, and other parameters.

Execution of a query is provider specific. In some providers, the actual query execution does not take place until IDirectorySearch::GetFirstRow or IDirectorySearch::GetNextRow is called. The IDirectorySearch interface works with search filters directly. Neither the SQL dialect nor the LDAP dialect are required.

The IDirectorySearch interface provides methods to enumerate the result set, row by row. The IDirectorySearch::GetFirstRow method retrieves the first row and IDirectorySearch::GetNextRow moves you to the next row from the current row. When you have reached the last row, calling these methods returns the S_ADS_NOMORE_ROWS error code. On the other hand, IDirectorySearch::GetPreviousRow moves you back one row at a time. And an S_ADS_NOMORE_ROWS return value indicates that you have reached the first row of the result set. These methods operate on the result set resident in memory on the client. Thus, when paged and asynchronous searches are performed and the _CACHE_RESULTS option is turned off, backward scrolling could have unexpected consequences.

Once you have located the appropriate row, you can call IDirectorySearch::GetColumn to obtain data items, column by column. For each call, you pass in the name of the column of interest. The data item returned is a pointer to an ADS_SEARCH_COLUMN structure. GetColumn allocates this structure for you, but you must free it, using FreeColumn. You must also call CloseSearchHandle to finish the search operation.

SetSearchPreference
Sets options for conducting a search.
ExecuteSearch
Executes an individual search.
AbandonSearch
Abandons search already under way.
GetFirstRow
Gets the first row of the search result.
GetNextRow
Gets the next row of the search result.
GetPreviousRow
Gets the previous row of the search result.
GetNextColumnName
Gets the name of the next column of the search result.
GetColumn
Gets the item in a specified column from the current row of the search result.
FreeColumn
Frees the ADS_SEARCH_COLUMN structure created by GetColumn.
CloseSearchHandle
Releases the search result from memory.

To perform a directory search

  1. Bind to an LDAP provider (it may be a domain controller or a global catalog provider).
  2. Retrieve the IDirectorySearch COM Interface. This may be done with a call to QueryInterface, or it may have been done in Step 1 during the initial binding.

    Optionally, call SetSearchPreference to select options for handling the results of your search.

  3. Call ExecuteSearch. Depending on the options set in SetSearchPreference this may or may not begin execution of the query.
  4. Call GetNextRow to move the row index (internal to IDirectorySearch) to the first row.
  5. Read the data from the row using GetColumn, then call FreeColumn to release the memory allocated by GetColumn.
  6. Repeat Step 5 until finished retrieving data from the search result OR until GetNextRow returns S_ADS_NOMORE_ROWS
  7. When done, call AbandonSearch and CloseSearchHandle.

As an example of the scenario above, start out binding to ADSI by calling the ADsOpenObject function:

HRESULT hr; // COM result variable
ADS_SEARCH_COLUMN col;  // COL for iterations
 
// nterface Pointers.
IDirectorySearch     *pDSSearch    =NULL;
IADs        *pObj    = NULL;
IADs        *pIADs    = NULL;
 
//Initialize COM.
CoInitialize(0);
 
//Open a connection with server.
hr = ADsGetObject(L"LDAP://coho.salmon.Microsoft.com", 
IID_IDirectorySearch,
(void **)&pDSSearch);

This provides a pointer to the IDirectorySearch interface.

Now that there's a COM interface for an IDirectoryInterface instance, you may wish to call IDirectorySearch::SetSearchPreference. The way to do this is covered in detail in the next section.

Next, you will need to build an array of attribute names to prepare to call the IDirectorySearch::ExecuteSearch function. The attribute names are defined within the schema of Active Directory. Refer to the ADSI section of the Platform SDK documentation to see the schema definition. The attribute names listed are returned (if supported by the object) as the result set of the search.

LPWSTR pszAttr[] = { L"description", L"Name", L"distinguishedname" };
ADS_SEARCH_HANDLE hSearch;
DWORD dwCount = 0;
DWORD dwAttrNameSize = sizeof(pszAttr)/sizeof(LPWSTR);

Now, call the ExecuteSearch function. The search does not actually take place, however, until you call the GetNextRow method.

//Search for all objects with the 'cn' property beginning with c.
hr = pDSSearch->ExecuteSearch(L"(cn=c*)",pszAttr ,dwAttrNameSize,&hSearch );

Now call GetNextRow to iterate through the rows in the result. Each row is then queried for the "description" attribute. If the attribute is found, it is displayed.

LPWSTR pszColumn;
    while( pDSSearch->GetNextRow( hSearch) != S_ADS_NOMORE_ROWS )
    {
        //Get the property.
        hr = pDSSearch->GetColumn( hSearch, L"description", &col );
 
        //If this object SUPPORTS this attribute, display it.
        if ( SUCCEEDED(hr) )
        { 
wprintf(L"The description property:%s\r\n",col.pADsValues->CaseIgnoreString); 
            pDSSearch->FreeColumn( &col );
        }
        else
            puts("description property NOT available");
        puts("------------------------------------------------");
        dwCount++;
    }
    pDSSearch->CloseSearchHandle(hSearch);

You can call the AbandonSearch method to end the search at any time.

Note  If a page size is NOT set, GetNextRow will block until the entire result set is returned to the client. If page size is set, then GetNextRow will block until the first page (page size = number of rows in a page) is returned. If page size is set and the query is to be sorted and you are NOT searching on at least one indexed attribute, then the page size value is ignored, and the server calculates the entire result set prior to returning the data. This has the effect of GetNextRow blocking until the query completes.

To change this query from a directory search to a global catalog search, the AdsOpenObject call is changed:

    //Open a connection with server.
    hr = ADsOpenObject(L"GC://coho.salmon.Microsoft.com", 
    L"Administrator", 
    L"", 
    ADS_SECURE_AUTHENTICATION,
    IID_IDirectorySearch,
    (void **)&pDSSearch);