Platform SDK: Active Directory, ADSI, and Directory Services

Example Code Using ADS_SEARCHPREF_DIRSYNC

The following sample code uses the ADSI implementation of the directory synchronization (DirSync) control to search the local domain partition of Active Directory for user objects that have changed since the previous call.

The example uses the IDirectorySearch interface to search from the root of the domain partition. Before calling the ExecuteSearch method, the example calls the SetSearchPreference method to specify the ADS_SEARCHPREF_SEARCH_SCOPE, ADS_SEARCHPREF_DIRSYNC, and ADS_SEARCHPREF_TOMBSTONE search preferences. The scope must specify a subtree search. With the ADS_SEARCHPREF_DIRSYNC search preference, you must also specify an ADS_PROV_SPECIFIC structure containing the length of the cookie and a pointer to it.

The first time this program is called, it specifies a NULL cookie and a length of zero. This causes the search operation to perform a full read, returning all the requested attributes for all objects that match the filter. Along with the search results, the server returns a valid cookie and the cookie length. On subsequent runs, the program retrieves the cached cookie and length and uses them to retrieve changes since the previous run.

Note that this sample simply caches the cookie and cookie length in the registry. In a real synchronization application, you must store the parameters in the same storage that you are keeping consistent with Active Directory. This ensures that the parameters and object data remain in sync if your database is ever restored from a backup.

#include <windows.h>
#include <stdio.h>
#include <activeds.h>

typedef struct {
    WCHAR objectGUID[40];
    WCHAR ADsPath[MAX_PATH];
    WCHAR phoneNumber[32];
    BOOL  isDeleted;
} MyUserData;

// forward declaration
VOID BuildGUIDString(WCHAR *szGUID, LPBYTE pGUID);
VOID WriteObjectDataToStorage(MyUserData *userdata, BOOL bUpdate);

//********************************************************************
// DoDirSyncSearch
//********************************************************************
HRESULT DoDirSyncSearch(
            LPWSTR pszSearchFilter,  // Search filter
            LPWSTR *pAttributeNames, // Attributes to retrieve
            DWORD dwAttributes,      // Number of attributes
            PUCHAR *ppCookie,        // Pointer to previous cookie
            PULONG pulCookieLength,  // Length of previous cookie
            LPWSTR szDC)             // Name of DC to bind to
{
LPOLESTR szDSPath = new OLECHAR[MAX_PATH];
LPOLESTR szServerPath = new OLECHAR[MAX_PATH];

IADs *pRootDSE = NULL;

IDirectorySearch *pSearch = NULL;
ADS_SEARCH_HANDLE hSearch = NULL;
ADS_SEARCHPREF_INFO arSearchPrefs[3];
ADS_PROV_SPECIFIC dirsync;
ADS_SEARCH_COLUMN col;

HRESULT hr;
VARIANT var;
DWORD i;
MyUserData userdata;
BOOL bUpdate = FALSE;
DWORD dwCount = 0;

// Validate input parameters.
if (!pulCookieLength || !ppCookie || !szDC) {
    wprintf(L"Invalid parameter.\n");
    return E_FAIL;
}

// If cookie is non-NULL, this is an update. Otherwise, it's a full read.
if (*ppCookie)
    bUpdate = TRUE;

CoInitialize(NULL);

// If we have a DC name from the previous USN sync, 
// include it in the binding string.
if (szDC[0]) {
    wcscpy(szServerPath, L"LDAP://");
    wcscat(szServerPath, szDC);
    wcscat(szServerPath, L"/");
} else
    wcscpy(szServerPath, L"LDAP://");

// Bind to root DSE.
wcscpy(szDSPath, szServerPath);
wcscat(szDSPath, L"rootDSE");
wprintf(L"RootDSE binding string: %s\n", szDSPath);
hr = ADsGetObject(szDSPath, 
                  IID_IADs,
                  (void**)&pRootDSE);
if (FAILED(hr)) {
    wprintf(L"failed to bind to rootDSE: 0x%x\n", hr);
    goto cleanup;
}

// Save the name of the DC that we connected to so we can connect to 
// the same DC on the next dirsync operation.
if (! szDC[0])
{
    hr = pRootDSE->Get(L"DnsHostName",&var);
    wcscpy(szDC, var.bstrVal);
    wcscpy(szServerPath, L"LDAP://");
    wcscat(szServerPath, szDC);
    wcscat(szServerPath, L"/");
}

// Get an IDirectorySearch pointer to the root of the domain partition.
hr = pRootDSE->Get(L"defaultNamingContext",&var);
wcscpy(szDSPath, szServerPath);
wcscat(szDSPath, var.bstrVal);
hr = ADsGetObject(szDSPath, IID_IDirectorySearch, (void**) &pSearch);
if (FAILED(hr)) {
    wprintf(L"failed to get IDirectorySearch: 0x%x\n", hr);
    goto cleanup;
}

// Initialize the structure to pass in the cookie.
// On the first call, the cookie is NULL and the length is zero.
// On later calls, the cookie and length are the values returned by 
// the previous call.
dirsync.dwLength = *pulCookieLength;
dirsync.lpValue = *ppCookie;
 
arSearchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE; 
arSearchPrefs[0].vValue.dwType = ADSTYPE_INTEGER; 
arSearchPrefs[0].vValue.Integer = ADS_SCOPE_SUBTREE; 

arSearchPrefs[1].dwSearchPref = ADS_SEARCHPREF_DIRSYNC; 
arSearchPrefs[1].vValue.dwType = ADSTYPE_PROV_SPECIFIC;
arSearchPrefs[1].vValue.ProviderSpecific = dirsync;

hr = pSearch->SetSearchPreference(arSearchPrefs, 2);
if (FAILED(hr)) {
    wprintf(L"failed to set search prefs: 0x%x\n", hr);
    goto cleanup;
}

// Search for the objects indicated by the search filter.
hr = pSearch->ExecuteSearch(pszSearchFilter,
                    pAttributeNames, dwAttributes, &hSearch );
if (FAILED(hr)) {
    wprintf(L"failed to set execute search: 0x%x\n", hr);
    goto cleanup;
}

// Loop through the rows of the search result.
// Each row is an object that has changed since the previous call
hr = pSearch->GetNextRow( hSearch);
while ( SUCCEEDED(hr) && hr != S_ADS_NOMORE_ROWS )
{
    ZeroMemory(&userdata, sizeof(MyUserData) );

    // Get the ADsPath.
    hr = pSearch->GetColumn( hSearch, L"ADsPath", &col );
    if ( SUCCEEDED(hr) ) {
        wcscpy(userdata.ADsPath, col.pADsValues->CaseIgnoreString);
        pSearch->FreeColumn( &col );
    }

    // Get the telephone number.
    hr = pSearch->GetColumn( hSearch, L"telephoneNumber", &col );
    if ( SUCCEEDED(hr) ) {
        wcscpy(userdata.phoneNumber, col.pADsValues->CaseIgnoreString);
        pSearch->FreeColumn( &col );
    }

    // Get the objectGUID number.
    hr = pSearch->GetColumn( hSearch, L"objectGUID", &col );
    if ( SUCCEEDED(hr) ) {
        WCHAR szGUID[40]; // string version of the objectGUID attribute
        if (col.pADsValues->OctetString.lpValue) {
            BuildGUIDString(szGUID, (LPBYTE) col.pADsValues->OctetString.lpValue);
            wcscpy(userdata.objectGUID, szGUID);
        }
        pSearch->FreeColumn( &col );
    }

    // Get the isDeleted attribute.
    hr = pSearch->GetColumn( hSearch, L"isDeleted", &col );
    if ( SUCCEEDED(hr) ) {
        userdata.isDeleted = col.pADsValues->Boolean;
        pSearch->FreeColumn( &col );
    }

    WriteObjectDataToStorage(&userdata, bUpdate);
    dwCount++;
    hr = pSearch->GetNextRow( hSearch);
}
wprintf(L"dwCount: %d\n", dwCount);

// After looping through the results, get the cookie.
if (hr == S_ADS_NOMORE_ROWS ) 
{
    hr = pSearch->GetColumn( hSearch, ADS_DIRSYNC_COOKIE, &col );
    if ( SUCCEEDED(hr) ) {
        wprintf(L"Got cookie\n");
        *pulCookieLength = col.pADsValues->ProviderSpecific.dwLength;
        *ppCookie = (PUCHAR) AllocADsMem (*pulCookieLength);
        memcpy(*ppCookie, col.pADsValues->ProviderSpecific.lpValue, 
            *pulCookieLength);
        pSearch->FreeColumn( &col );
    } else
        wprintf(L"no cookie: 0x%x\n", hr);
}

cleanup:
if (pRootDSE)
    pRootDSE->Release();
if (pSearch)
    pSearch->Release();
if (hSearch)
    pSearch->CloseSearchHandle(hSearch);
VariantClear(&var);
CoUninitialize();

return hr;
}


//********************************************************************
// WriteObjectDataToStorage routine
//********************************************************************
VOID WriteObjectDataToStorage(MyUserData *userdata, BOOL bUpdate)
{
if (bUpdate)
    wprintf(L"UPDATE:\n");
else
    wprintf(L"INITIAL DATA:\n");
wprintf(L"   objectGUID: %s\n", userdata->objectGUID);
wprintf(L"   ADsPath: %s\n", userdata->ADsPath);
wprintf(L"   phoneNumber: %s\n", userdata->phoneNumber);
if (userdata->isDeleted)
    wprintf(L"   DELETED OBJECT\n");
wprintf(L"---------------------------------------------\n");
return;

}

//********************************************************************
// WriteCookieAndDCtoStorage routine
// This example simply caches the cookie in the registry. In a real 
// synchronization application, you must store these parameters in the
// same storage that you are keeping consistent with Active Directory.
// This ensures that the parameters and object data remain in sync if 
// the storage is ever restored from a backup.
//********************************************************************
DWORD WriteCookieAndDCtoStorage(
            UCHAR *pCookie, 
            ULONG ulLength,
            WCHAR *pszDCName) 
{
HKEY hReg = NULL;
DWORD dwStat = NO_ERROR;

// Create a registry key under 
//     HKEY_CURRENT_USER\SOFTWARE\Vendor\Product.
dwStat = RegCreateKeyExW(HKEY_CURRENT_USER,
            L"Software\\Microsoft\\Windows 2000 AD-Synchro-DirSync",
            0,
            NULL,
            REG_OPTION_NON_VOLATILE,
            KEY_ALL_ACCESS,
            NULL,
            &hReg,
            NULL);
if (dwStat != NO_ERROR) {
    wprintf(L"RegCreateKeyEx failed: 0x%x\n", dwStat);
    return dwStat;
}

// Cache the cookie as a value under the registry key.
dwStat = RegSetValueExW(hReg, L"Cookie", 0, REG_BINARY,
                           (const BYTE *)pCookie, ulLength);
if (dwStat != NO_ERROR)
    wprintf(L"RegSetValueEx for cookie failed: 0x%x\n", dwStat);

// Cache the cookie length as a value under the registry key.
dwStat = RegSetValueExW(hReg, L"Cookie Length", 0, REG_DWORD,
                           (const BYTE *)&ulLength, sizeof(DWORD) );
if (dwStat != NO_ERROR)
    wprintf(L"RegSetValueEx for cookie length failed: 0x%x\n", dwStat);

// Cache the DC name as a value under the registry key.
dwStat = RegSetValueExW(hReg, L"DC name", 0, REG_SZ,
                           (const BYTE *)pszDCName, 2*(wcslen(pszDCName)) );
if (dwStat != NO_ERROR)
    wprintf(L"RegSetValueEx for DC name failed: 0x%x\n", dwStat);

RegCloseKey(hReg);
return dwStat;
}


//********************************************************************
// GetCookieAndDCfromStorage routine
//********************************************************************
DWORD GetCookieAndDCfromStorage(
            PUCHAR *ppCookie,        // Receives pointer to cookie
            PULONG pulCookieLength,  // Receives length of cookie
            WCHAR *pszDCName)       // Receives name of DC to bind to
{
HKEY hReg = NULL;
DWORD dwStat;
DWORD dwLen;

// Open the registry key.
dwStat = RegOpenKeyExW(
            HKEY_CURRENT_USER,
            L"Software\\Microsoft\\Windows 2000 AD-Synchro-DirSync",
            0,
            KEY_QUERY_VALUE,
            &hReg);
if (dwStat != NO_ERROR) {
    wprintf(L"RegOpenKeyEx failed: 0x%x\n", dwStat);
    return dwStat;
}

// Get the length of the cookie from the registry.
dwLen = sizeof(DWORD);
dwStat = RegQueryValueExW(hReg, L"Cookie Length", NULL, NULL, 
                          (LPBYTE)pulCookieLength, &dwLen );
if (dwStat != NO_ERROR) {
    wprintf(L"RegQueryValueEx failed to get length: 0x%x\n", dwStat);
    return dwStat;
}

// Allocate a buffer for the cookie value.
*ppCookie = (PUCHAR) GlobalAlloc(GPTR, *pulCookieLength);
if (!*ppCookie) {
    wprintf(L"GlobalAlloc failed: %u\n", GetLastError() );
    return dwStat;
}

// Now get the cookie from the registry.
dwStat = RegQueryValueExW(hReg, L"Cookie", NULL, NULL, 
                         (LPBYTE)*ppCookie, pulCookieLength );
if (dwStat != NO_ERROR) {
    wprintf(L"RegQueryValueEx failed to get cookie: 0x%x\n", dwStat);
    return dwStat;
}

// Get the DC name from the registry.
dwLen = MAX_PATH;
dwStat = RegQueryValueExW(hReg, L"DC name", NULL, NULL, 
                         (LPBYTE)pszDCName, &dwLen );
if (dwStat != NO_ERROR) {
    wprintf(L"RegQueryValueEx failed to get DC name: 0x%x\n", dwStat);
    return dwStat;
}

RegCloseKey(hReg);

return NO_ERROR;
}

//********************************************************************
// BuildGUIDString
// Routine that makes the GUID into a string in directory service bind form
//********************************************************************
VOID 
BuildGUIDString(WCHAR *szGUID, LPBYTE pGUID)
{
    DWORD i = 0x0;
    DWORD dwlen = sizeof(GUID);
    WCHAR buf[4];

    wcscpy(szGUID, L"");

    for (i;i<dwlen;i++) {
        wsprintf(buf, L"%02x", pGUID[i]);
        wcscat(szGUID, buf);
    }
}

//********************************************************************
// main
//********************************************************************
int main(int argc, char* argv[])
{
DWORD dwStat;
ULONG ulLength;
UCHAR *pCookie;
WCHAR szDCName[MAX_PATH];
HRESULT hr;
LPWSTR szAttribs[] = { 
    {L"telephoneNumber"}
};
LPWSTR *pszAttribs=szAttribs;
DWORD dwAttribs = sizeof(szAttribs)/sizeof(LPWSTR);

// Get all attributes.
if (argc>1) 
    if (argv[1][0] == 'a') {
        pszAttribs=NULL;
        dwAttribs = -1;
    }

if (argc>2) 
{
    // Perform a full synchronization.
    // Initialize the synchronization parameters to zero or NULL.
    wprintf(L"Performing a full sync.\n");
    szDCName[0] = '\0';
    ulLength = 0;
    pCookie = NULL;
} else
{
    // Perform an incremental synchronization.
    // Initialize synchronization parameters from storage.
    wprintf(L"Retrieving changes only.\n");
    dwStat = GetCookieAndDCfromStorage(&pCookie, &ulLength, szDCName);
    if (dwStat != NO_ERROR) {
        wprintf(L"Could not get the cookie: %u\n", dwStat);
        goto cleanup;
    }
}

// Perform the search and update the synchronization parameters.
hr = DoDirSyncSearch(L"(&(objectClass=user)(objectCategory=person))",
                     pszAttribs, dwAttribs, 
                     &pCookie, &ulLength,
                     szDCName);
if (FAILED(hr)) {
    wprintf(L"DoDirSyncSearch failed: 0x%x\n", hr);
    goto cleanup;
}

// Cache the returned synchronization parameters in storage.
wprintf(L"Caching the synchronization parameters.\n");
dwStat = WriteCookieAndDCtoStorage(pCookie, ulLength, szDCName);
if (dwStat != NO_ERROR) {
    wprintf(L"Could not cache the cookie: %u\n", dwStat);
    goto cleanup;
}

cleanup:
if (pCookie)
    GlobalFree (pCookie);

return 1;
}