Platform SDK: Active Directory, ADSI, and Directory Services

How Clients Find and Use a Service Connection Point

The following sample code shows how a client application searches the global catalog for an SCP. In this sample, the client application has a hard-coded GUID string that identifies the service. The service's installation program stored the same GUID string as one of the values of the SCPs multi-valued keywords attribute.

This sample actually consists of two routines. The GetGC routine retrieves an IDirectorySearch pointer for a global catalog (GC). The ScpLocate routine uses the IDirectorySearch methods to search the GC.

The GC contains a partial replica of every object in the forest, but it does not contain all of the SCP attributes that the client needs. First, the client must search the GC to find the SCP and retrieve its DN. Then the client uses the SCP's DN to bind to an IDirectoryObject pointer on the SCP. The client then calls the IDirectoryObject::GetObjectAttributes method to retrieve the rest of the attributes.

DWORD
ScpLocate(
    TCHAR *pszDN,                 // Returns distinguished name of SCP
    TCHAR *pszServiceDNSName,     // Returns service's DNS name
    TCHAR *pszServiceDNSNameType, // Returns type of DNS name
    TCHAR *pszClass,              // Returns name of service class
    USHORT *pusPort)              // Returns service port
{
HRESULT hr;

// Params for IDirectoryObject
LPOLESTR szSCPPath = new OLECHAR[MAX_PATH]; 
IDirectoryObject *pSCP = NULL;
ADS_ATTR_INFO       *pPropEntries = NULL;
DWORD dwNumAttrGot;

// Structures and Params for IDirectorySearch
IDirectorySearch    *pSearch = NULL;
DWORD               dwPref, dwAttrs;
TCHAR               szQuery[255];
ADS_SEARCH_COLUMN   Col;
ADS_SEARCH_HANDLE   hSearch = NULL;
ADS_SEARCHPREF_INFO SearchPref[2];

// Properties to retrieve from the SCP object.
TCHAR   *szAttribs[]={
        {TEXT("distinguishedName")},
        {TEXT("serviceClassName")},
        {TEXT("serviceDNSName")},
        {TEXT("serviceDNSNameType")},
        {TEXT("serviceBindingInformation")}
    };

// First, get an IDirectorySearch pointer for the global catalog. 
// See the GetGC sample code below. 
hr = GetGC(&pSearch);
if (FAILED(hr)) {
    ReportError(TEXT("GetGC failed"), 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);
fprintf (stderr, "SetSearchPreference: 0x%x\n", hr);
if (FAILED(hr))    {
    fprintf (stderr, "Failed to set search prefs: hr:0x%x\n", hr);
    goto Cleanup;
} 

// Search for an exact match on our product GUID.
_tcscpy(szQuery,
         TEXT("keywords=A762885A-AA44-11d2-81F1-00C04FB9624E"));

// 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,
                            szAttribs,
                            1,
                            &hSearch);
fprintf (stderr, "ExecuteSearch: 0x%x\n", hr);
if (FAILED(hr)) {
    ReportError(TEXT("ExecuteSearch failed."), hr);
    goto Cleanup;
} 

// Loop through the results. Each row should be an instance of the 
// service identified by the product GUID.
// TODO: Add logic to select from multiple service instances.
hr = pSearch->GetNextRow(hSearch);
while (SUCCEEDED(hr) && hr !=S_ADS_NOMORE_ROWS) 
{
    hr = pSearch->GetColumn(hSearch, TEXT("distinguishedName"), &Col);
    if (FAILED(hr)) 
        break;
    _tcscpy(pszDN, Col.pADsValues->CaseIgnoreString);
    pSearch->FreeColumn(&Col);
    hr = pSearch->GetNextRow(hSearch);
}

// Now bind to the DN to get the other properties.
wcscpy(szSCPPath, L"LDAP://");
wcscat(szSCPPath, pszDN);
hr = ADsGetObject(szSCPPath,
                  IID_IDirectoryObject,
                  (void**)&pSCP);
if (SUCCEEDED(hr)) 
{
    dwAttrs=sizeof(szAttribs)/sizeof(LPWSTR);
    hr = pSCP->GetObjectAttributes( szAttribs,
                                     dwAttrs,
                                     &pPropEntries,
                                     &dwNumAttrGot);
    if(FAILED(hr)) {
        ReportError(TEXT("GetObjectAttributes Failed."), hr);
        goto Cleanup;
    }

    // Loop through the entries returned by GetObjectAttributes 
    // and save the values in the appropriate buffers. 
    for (int i=0;i<(LONG)dwAttrs;i++) 
    {
        if (_tcscmp(TEXT("distinguishedName"), 
                    pPropEntries[i].pszAttrName) ==0) 
        {
            _tcscpy(pszDN, 
                    pPropEntries[i].pADsValues->CaseIgnoreString);
        }

        if (_tcscmp(TEXT("serviceDNSName"),
                    pPropEntries[i].pszAttrName)==0) 
        {
            _tcscpy(pszServiceDNSName, 
                     pPropEntries[i].pADsValues->CaseIgnoreString);
        }

        if (_tcscmp(TEXT("serviceDNSNameType"),
                    pPropEntries[i].pszAttrName)==0) 
        {
            _tcscpy(pszServiceDNSNameType, 
                     pPropEntries[i].pADsValues->CaseIgnoreString);
        }

        if (_tcscmp(TEXT("serviceClassName"),
                    pPropEntries[i].pszAttrName)==0) 
        {
            _tcscpy(pszClass, 
                    pPropEntries[i].pADsValues->CaseIgnoreString);
        }

        if (_tcscmp(TEXT("serviceBindingInformation"),
                    pPropEntries[i].pszAttrName)==0) 
        {
            *pusPort=(USHORT)_ttoi(
                    pPropEntries[i].pADsValues->CaseIgnoreString);
        }
    }
}

Cleanup:
if (pSCP) 
    pSCP->Release();
if (pPropEntries)
    FreeADsMem(pPropEntries);
if (hSearch)
    pSearch->CloseSearchHandle(hSearch);
return hr;
}

 
//***********************************************************
// GetGC
// Retrieves an IDirectorySearch pointer for a global catalog (GC)
//***********************************************************
HRESULT GetGC(IDirectorySearch **ppDS)
{
HRESULT hr;
IEnumVARIANT *pEnum = NULL;
IADsContainer *pCont = NULL;
VARIANT var;
IDispatch *pDisp = NULL;
ULONG lFetch;

// Set IDirectorySearch pointer to NULL.
*ppDS = NULL;

// First, bind to the GC: namespace container object. The "real" GC DN 
// is a single immediate child of the GC: namespace, which must 
// be obtained using enumeration.
hr = ADsOpenObject(TEXT("GC:"),
                NULL,
                NULL,
                ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
                IID_IADsContainer,
                (void**)&pCont);
if (FAILED(hr)) {
    _tprintf(TEXT("ADsOpenObject 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 (FAILED(hr)) {
    _tprintf(TEXT("ADsEnumerateNext failed: 0x%x\n"), hr);
    goto cleanup;
} 

if (( hr == S_OK ) && ( lFetch == 1 ) )
{
    pDisp = V_DISPATCH(&var);
    hr = pDisp->QueryInterface( IID_IDirectorySearch, (void**)ppDS); 
}

cleanup:

if (pEnum)
    ADsFreeEnumerator(pEnum);
if (pCont)
    pCont->Release();
if (pDisp)
    (pDisp)->Release();
return hr;
}