Searching with the IDirectorySearch Interface

The IDirectorySearch interface provides a high-level and low-overhead interface for querying data of a directory or a global catalog. The IDirectorySearch COM interface can be used only with a vtable, and thus, is not available to Automation-based development environments.

To perform a search

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

For more information about the search filter syntax, see Search Filter Syntax.

Query execution is provider-specific. With some providers, the actual query execution does not occur 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. Conversely, IDirectorySearch::GetPreviousRow moves you back one row at a time. 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 can have unexpected consequences.

When you have located the appropriate row, call IDirectorySearch::GetColumn to obtain data items, column by column. For each call, you pass 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. Call CloseSearchHandle to complete the search operation.

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 with a call to QueryInterface; this operation 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 query execution.

  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 all data is retrieved from the search result, or until GetNextRow returns S_ADS_NOMORE_ROWS.

  7. Call AbandonSearch and CloseSearchHandle when complete.

The following code example shows this scenario. To start binding to ADSI, call the ADsOpenObject function.

HRESULT hr = S_OK; // COM result variable
ADS_SEARCH_COLUMN col;  // COL for iterations
LPWSTR szUsername = NULL; // user name
LPWSTR szPassword = NULL; // password
// Interface Pointers.
IDirectorySearch     *pDSSearch    =NULL;
// Initialize COM.

// Add code to securely retrieve the user name and password or
// leave both as NULL to use the default security context.
// Open a connection with server.
hr = ADsOpenObject(L"LDAP://", 
(void **)&pDSSearch);

This provides a pointer to the IDirectorySearch interface.

Now that there is a COM interface for an IDirectoryInterface instance, call IDirectorySearch::SetSearchPreference.

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. For more information about the schema definition, see ADSI Schema Model. 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" };
DWORD dwCount = 0;
DWORD dwAttrNameSize = sizeof(pszAttr)/sizeof(LPWSTR);

Now, call the ExecuteSearch function. The search does not run until you call the GetNextRow method.

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

Call GetNextRow to iterate 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) )
           if (col.dwADsType == ADSTYPE_CASE_IGNORE_STRING)
              wprintf(L"The description property:%s\r\n", col.pADsValues->CaseIgnoreString); 
           pDSSearch->FreeColumn( &col );
            puts("description property NOT available");

To end the search, call the AbandonSearch method.

Be aware that if a page size is not set, GetNextRow blocks until the entire result set is returned to the client. If page size is set, then GetNextRow blocks 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 the server.
hr = ADsOpenObject(L"GC://", 
(void **)&pDSSearch);