Platform SDK: Active Directory, ADSI, and Directory Services

Enumerating the Replicas of a Service

This topic shows sample code that enumerates the installed instances of a replicated service on different host computers throughout an enterprise. To change the service account password on each instance of a replicated service, you could use this code in conjunction with the code sample in the Changing the Password on a Service's User Account topic.

The sample assumes that each service instance has its own service connection point (SCP) object in the directory. An SCP is an object of the serviceConnectionPoint class. This class has a keywords attribute, which is a multi-valued attribute that is replicated to all global catalogs (GCs) in the forest. The keywords attribute of each instance's SCP contains the service's product GUID. This makes it easy to find all of the SCPs for the various service instances by simply searching a GC for objects with a keywords attribute that equals the product GUID.

The example code gets an IDirectorySearch pointer to a GC, and uses the IDirectorySearch::ExecuteSearch method to search for the SCPs. Note that the GC contains a partial replica of each SCP. This means that it contains some of the SCP's attributes, but not all. In this example, we are interested in the serviceDNSName attribute, which contains the DNS name of the host server for that service instance. Because serviceDNSName is not one of the attributes that is replicated in a GC, the example uses a two step process to retrieve it. First it uses the GC search to get the distinguished name (DN) of the SCP, then it uses that DN to bind directly to the SCP to retrieve the serviceDNSName property.

HRESULT EnumerateServiceInstances(
       LPTSTR szQuery,                // Search string filter
       TCHAR **pszAttribs,            // Array of attributes to retrieve
       DWORD dwAttribs,               // Number of attributes requested
       DWORD *pdwAttribs,             // Number of attributes retrieved
       ADS_ATTR_INFO **ppPropEntries  // Returns pointer to retrieved attributes
        )
{
HRESULT hr;
IEnumVARIANT *pEnum = NULL;
IADsContainer *pCont = NULL;
VARIANT var;
IDispatch *pDisp = NULL;
BSTR  bstrPath;
ULONG lFetch;
IADs *pADs = NULL;
int iRows=0;
static IDirectorySearch *pSearch = NULL;
static ADS_SEARCH_HANDLE hSearch = NULL;
// Params for IDirectoryObject
 
TCHAR szSCPPath[MAX_PATH]; 
IDirectoryObject *pSCP = NULL;
 
// Structures and Params for IDirectorySearch
DWORD               dwPref;
ADS_SEARCH_COLUMN   Col;
ADS_SEARCHPREF_INFO SearchPref[2];
 
// First time through, set up the search.
if (pSearch == NULL) 
{
    // Bind to the GC: namespace container object. The "real" GC DN 
    // is a single immediate child of the GC: namespace, which we must 
    // obtain via enumeration.
    hr = ADsGetObject(TEXT("GC:"), 
        IID_IADsContainer, 
        (void**) &pCont );
    if (FAILED(hr)) {
        _tprintf(TEXT("ADsGetObject(GC) failed: 0x%x\n"), hr);
        goto Cleanup;
    }
 
    // Fetch an enumeration interface for the GC container. 
    hr = ADsBuildEnumerator(pCont,&pEnum);
    if (FAILED(hr)) {
        _tprintf(TEXT("ADsBuildEnumerator failed: 0x%x\n"), hr);
        goto Cleanup;
    }
 
    // Now enumerate. There's only one child of the GC: object.
    hr = ADsEnumerateNext(pEnum,1,&var,&lFetch);
    if (( hr == S_OK ) && ( lFetch == 1 ) ) 
    {
        pDisp = V_DISPATCH(&var);
        hr = pDisp->QueryInterface( IID_IADs, (void**)&pADs);
        if (hr == S_OK) 
            hr = pADs->get_ADsPath(&bstrPath);
    }
    if (FAILED(hr)) {
        _tprintf(TEXT("Enumeration failed: 0x%x\n"), hr);
        goto Cleanup;
    }
 
    // At this point bstrPath contains the ADsPath for the current GC.  
    // Now bind the GC to get the search interface.
    hr = ADsGetObject(bstrPath, IID_IDirectorySearch, (void**)&pSearch);
    if (FAILED(hr)) {
        _tprintf(TEXT("Failed to bind search root: 0x%x\n"), hr);
        goto Cleanup;
    } 
 
    // Set up the search. We want to do a deep search.
    // Note that we are not expecting thousands of objects
    // in this example, so we will ask for 1000 rows / page.
    dwPref=sizeof(SearchPref)/sizeof(ADS_SEARCHPREF_INFO);
    SearchPref[0].dwSearchPref =    ADS_SEARCHPREF_SEARCH_SCOPE;
    SearchPref[0].vValue.dwType =   ADSTYPE_INTEGER;
    SearchPref[0].vValue.Integer =  ADS_SCOPE_SUBTREE;
 
    SearchPref[1].dwSearchPref =    ADS_SEARCHPREF_PAGESIZE;
    SearchPref[1].vValue.dwType =   ADSTYPE_INTEGER;
    SearchPref[1].vValue.Integer =  1000;
 
    hr = pSearch->SetSearchPreference(SearchPref, dwPref);
    if (FAILED(hr))    {
        _tprintf(TEXT("Failed to set search prefs: 0x%x\n"), hr);
        goto Cleanup;
    } 
 
    // Execute the search. From the GC we can get the distinguished name 
    // of the SCP. Use the DN to bind to the SCP and get the other 
    // properties.
    hr = pSearch->ExecuteSearch(szQuery, pszAttribs, 1, &hSearch);
    if (FAILED(hr)) {
        _tprintf(TEXT("ExecuteSearch failed: 0x%x\n"), hr);
        goto Cleanup;
    } 
}
 
// Get the next row.
hr = pSearch->GetNextRow(hSearch);
 
// Process the row.
if (SUCCEEDED(hr) && hr !=S_ADS_NOMORE_ROWS) 
{
    // Get the distinguished name of the object in this row.
    hr = pSearch->GetColumn(hSearch, TEXT("distinguishedName"), &Col);
    if FAILED(hr) { 
        _tprintf(TEXT("GetColumn failed: 0x%x\n"), hr);
        goto Cleanup;
    }
 
    // Bind to the DN to get the properties.
    wcscpy(szSCPPath, L"LDAP://");
    wcscat(szSCPPath, Col.pADsValues->CaseIgnoreString);
    hr = ADsGetObject(szSCPPath, IID_IDirectoryObject, (void**)&pSCP);
    if (SUCCEEDED(hr)) 
    {
        hr = pSCP->GetObjectAttributes(pszAttribs, dwAttribs,
                          ppPropEntries, pdwAttribs);
        if(FAILED(hr)) {
            _tprintf(TEXT("GetObjectAttributes Failed."), hr);
            goto Cleanup;
        }
    pSearch->FreeColumn(&Col);
    }
}
 
Cleanup:
if (pSCP) 
    pSCP->Release();
if (pCont) 
    pCont->Release();
if (pEnum)
    ADsFreeEnumerator(pEnum);
if (pADs) 
    pADs->Release();
if (pDisp)
    pDisp->Release();
 
return hr;
 
}