By far the easiest and most common way of searching Active Directory is to use ADO and the ADSI OLE DB provider. Of course, C and C++ developers can use ADO or OLE DB from their applications, but they might prefer the ADSI IDirectorySearch interface, which is a low-level interface for searching Active Directory.
As I mentioned at the beginning of this chapter, the ADSI OLE DB provider uses IDirectorySearch to perform its work. IDirectorySearch and IDirectoryObject (explained in Chapter 7) are the only two interfaces in ADSI that don't support Automation—that is, they don't support IDispatch, cannot be used by the scripting languages, and are difficult to use from Visual Basic. Since this book is targeted to readers using a variety of development environments, I wanted to give an example of how you can use this interface directly using C and C++. The source code is simple enough and is similar to the VBScript version of the Phone sample. Listing 5-2 shows the code, which is also included on the companion CD.
#define _WIN32_WINNT 0x0500
#define _UNICODE
#include <stdio.h>
#include <tchar.h> #include <objbase.h>
#include <activeds.h>
// Attributes to return
_TCHAR *rgpszAttributeList[] = { _TEXT("cn"), _TEXT("telephoneNumber") };
// LDAP search filter
_TCHAR *pszSearchFormat = _TEXT("(&(objectCategory=person)(sn=%s*))");
//—————————————————————————————————-
// wmain ( int argc, wchar_t *argv[] )
// Entry point for UNICODE console mode apps
//—————————————————————————————————-
void wmain( int argc, wchar_t *argv[] )
{
// Create buffer for the query string
_TCHAR *pszQuery = new _TCHAR[_MAX_PATH];
// Initialize the buffer
_tcscpy(pszQuery, _TEXT(""));
// Loop through all the command line arguments
if (argc > 1)
{
// Copy the name to search from the command line argument array
_tcscpy(pszQuery, argv[1]);
// Initialize variables
HRESULT hResult;
IADs *pobjIADs;
VARIANT varDomain;
// Initialize COM
CoInitialize(NULL);
// Get a base IADs object
hResult = ADsGetObject(_TEXT("GC://rootDSE"), IID_IADs,
(void**)&pobjIADs);
// Use the Get method to get the default naming context
// (directory partition)
hResult = pobjIADs->Get(_TEXT("defaultNamingContext"), &varDomain);
// Build LDAP path to the domain
_TCHAR *pszLDAPPath = new _TCHAR[_MAX_PATH];
_tcscpy(pszLDAPPath, _TEXT("GC://"));
_tcscat(pszLDAPPath, varDomain.bstrVal);
// Get the directory search interface to the domain
IDirectorySearch *pContainerToSearch = NULL;
hResult = ADsGetObject( pszLDAPPath,
IID_IDirectorySearch,
(void **)&pContainerToSearch);
// Create search filter in LDAP format
_TCHAR *pszSearchFilter = new _TCHAR[_MAX_PATH];
// Create LDAP format search string
_stprintf(pszSearchFilter, pszSearchFormat, pszQuery);
// Variables for column name and data
ADS_SEARCH_COLUMN colSearchColumn;
// Create search preferences structure
ADS_SEARCHPREF_INFO arSearchPrefs[3];
// Set a subtree search
arSearchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
arSearchPrefs[0].vValue.dwType = ADSTYPE_INTEGER;
arSearchPrefs[0].vValue.Integer = ADS_SCOPE_SUBTREE;
// Set page size for 20 rows
arSearchPrefs[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
arSearchPrefs[1].vValue.dwType = ADSTYPE_INTEGER;
arSearchPrefs[1].vValue.Integer = 20;
// Turn sorting on
// Create a sort key using the cn attribute
ADS_SORTKEY adsSortKey;
adsSortKey.pszAttrType = _TEXT("cn"); // Attribute to sort on
adsSortKey.pszReserved = NULL; // Reserved, not used
adsSortKey.fReverseorder = 0; // Normal sort, not reversed
// Create the sort search preference
arSearchPrefs[2].dwSearchPref = ADS_SEARCHPREF_SORT_ON;
arSearchPrefs[2].vValue.dwType = ADSTYPE_PROV_SPECIFIC;
arSearchPrefs[2].vValue.ProviderSpecific.dwLength = sizeof(ADS_SORTKEY);
arSearchPrefs[2].vValue.ProviderSpecific.lpValue = (LPBYTE) &adsSortKey;
// Set the search preferences
hResult = pContainerToSearch->SetSearchPreference( &arSearchPrefs[0], 3);
// Handle to search results that is passed to other methods of
// IDirectorySearch.
ADS_SEARCH_HANDLE hSearch; // Execute search by providing LDAP filter, attribute list, and size
// Returns handle to search
hResult = pContainerToSearch->ExecuteSearch(pszSearchFilter,
rgpszAttributeList,
sizeof(rgpszAttributeList) / sizeof(LPOLESTR),
&hSearch);
if ( SUCCEEDED(hResult) )
{
// Call IDirectorySearch::GetNextRow() to retrieve the next
// row of data
hResult = pContainerToSearch->GetFirstRow(hSearch);
if (SUCCEEDED(hResult))
{
// Loop through each row returned
while (hResult != S_ADS_NOMORE_ROWS)
{
// Get and print the first attribute (common name)
hResult = pContainerToSearch->GetColumn(hSearch,
rgpszAttributeList[0], &colSearchColumn);
if (SUCCEEDED(hResult))
{
// Display the cn attribute
_tprintf(_TEXT("%s\t"),
colSearchColumn.pADsValues->CaseIgnoreString);
pContainerToSearch->FreeColumn( &colSearchColumn );
}
// Get and print Telephone Number
hResult = pContainerToSearch->GetColumn(hSearch,
rgpszAttributeList[1], &colSearchColumn);
if (SUCCEEDED(hResult))
{
// Display the phone number attribute
_tprintf(_TEXT("%s"),
colSearchColumn.pADsValues->CaseIgnoreString);
pContainerToSearch->FreeColumn( &colSearchColumn );
}
else // Didn't get phone number, display substitute text
_tprintf(_TEXT("(number not listed)")); // Start a new line
_tprintf(_TEXT("\n"));
//Get the next row
hResult = pContainerToSearch->GetNextRow(hSearch);
}
}
// Close the search handle to clean up
pContainerToSearch->CloseSearchHandle(hSearch);
}
// Clean up objects
if (pContainerToSearch)
pContainerToSearch->Release();
if (pobjIADs)
pobjIADs->Release();
// Uninitialize COM
CoUninitialize();
}
return;
}
Listing 5-2 Phone.cpp uses IDirectorySearch to search Active Directory.