Platform SDK: Active Directory, ADSI, and Directory Services

Code Walkthrough: Using OLE DB to Access Active Directory

The following sample code illustrates how to call Active Directory using COM, C++, and OLE DB. This sample is a console application which searches the directory for all users under the coho.salmon.Microsoft.com tree and displays the name and ADsPath properties to the console window.

ADSI defines two GUIDs for dialects:

The following sample uses the LDAP dialect.

You may also pass DBGUID_DEFAULT as the dialect. In this case, ADSI will try to use the SQL dialect first, if that fails, it will retry using the LDAP dialect.

For more information about OLE DB, refer to the OLE DB programming manual.

Variable Definitions

The following are COM Interface pointers for OLE DB:

IDBInitialize*       pIDBInitialize = NULL;
IDBCreateSession*    pCreateSession = NULL;
IDBCreateCommand*    pICreateCommand = NULL;
IRowset*             pIRowset = NULL;
ICommandText*        pICommandText = NULL;
IDBProperties*       pIDBProperties = NULL;
IColumnsInfo*        pIColumnsInfo = NULL;
IAccessor*           pIAccessor = NULL;
 
HACCESSOR            hAccessor = NULL;     //Accessor Handle.
DBCOLUMNINFO*        pDBColumnInfo;        //Column information for returned rows.   
DBPROP               InitProperties[4];    //Array for OLE DB connection info.
DBPROPSET            rgInitPropSet[1];     //Array for OLE DB connection info.
int                  i;
ULONG                cCol;
ULONG                cbColOffset = 0;
DBBINDING            rgBind[MAX_COL];      //Array for column binding info.
HROW                 hRows[5];             //Array of handles of returned rows.
HROW*                pRows = &hRows[0];    //Pointer to rows.
LONG                 cNumRows;             //Number of rows returned.
WCHAR*               pStringsBuffer;       //String buffer.
HRESULT              hr;                   //COM HRESULT variable.
ULONG                cMaxRowSize;          //Buffer size for one row's data.

Query String

In the following example, set up a query string using the LDAP dialect:

LPCTSTR wCmdString =OLESTR( "<LDAP://coho.salmon.Microsoft.com/cn=users,dc=coho,dc=salmon,dc=Microsoft,dc=com>;((objectClass=user)); name, adspath;subtree"
);
 
// Initialize The Component Object Module library
CoInitialize(NULL);

ADsDSOObject

Now the COM function CoCreateInstance is called. The class CLSID_ADsDSOObject refers to the OLE DB Active Directory provider. This function will return an IDBInitialize interface. This interface is the root of our OLE DB connection.

// Obtain access to the OLE DB - ODBC provider - CLSID_ADsDSOObject
hr =CoCreateInstance(CLSID_ADsDSOObject , NULL, CLSCTX_INPROC_SERVER, 
            IID_IDBInitialize, (void **) pIDBInitialize);

DBPROP Array/OLE DB Connection and Initialization

In preparation for calling the IDBProperties::SetProperties method, build an array of the properties for the OLE DB connection. The following code initializes the DBPROP INITPROPERTIES array, and fills in the user ID and password, and turns off user interface prompting (DBPROP_INIT_PROMPT).

for (i = 0; i < 3; i++ )
{
    VariantInit(&InitProperties[i].vValue);
    InitProperties[i].dwOptions = DBPROPOPTIONS_REQUIRED;
    InitProperties[i].colid = DB_NULLID;
}
 
// level of prompting that will be done to complete the connection process
InitProperties[0].dwPropertyID = DBPROP_INIT_PROMPT;
InitProperties[0].vValue.vt = VT_I2;
InitProperties[0].vValue.iVal = DBPROMPT_NOPROMPT;

Fill in the User Name

InitProperties[1].dwPropertyID = DBPROP_AUTH_USERID;
InitProperties[1].vValue.vt = VT_BSTR;
InitProperties[1].vValue.bstrVal = SysAllocString((LPOLESTR)L"user");

Fill in the Password

InitProperties[2].dwPropertyID = DBPROP_AUTH_PASSWORD;
InitProperties[2].vValue.vt = VT_BSTR;
InitProperties[2].vValue.bstrVal = SysAllocString((LPOLESTR)L"password");

Set Initialization Properties

Now, call QueryInterface to get the IDBInitialize interface for the IDBProperties interface. Call the IDBProperties::SetProperties method to pass the DBPROPSET structure, which contains a pointer to the InitProperties DBPROP structure that was filled in a previous step.

// Set our InitProperties array into the property set. 
rgInitPropSet[0].guidPropertySet = DBPROPSET_DBINIT;
rgInitPropSet[0].cProperties = 4;
rgInitPropSet[0].rgProperties = InitProperties;
 
// Set initialization properties.
pIDBInitialize->QueryInterface(IID_IDBProperties, (void **)&pIDBProperties);
pIDBProperties->SetProperties(1,rgInitPropSet);
pIDBProperties->Release();

Perform the Connection

Now, call the Initialize method to establish the connection This step actually hooks up IDBInitialize with the directory.

pIDBInitialize->Initialize();

Create a Session Object from the IDBInitialize Interface

hr = pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void **) &pCreateSession);

Create an IDBCreateCommand Interface from the IDBCreateSession Interface

hr = pCreateSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown **) &pICreateCommand);

Create an ICommandText Interface from the ICreateCommand Interface

hr = pICreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown **) &pICommandText);

Set the Command Text

Before executing the query, pass the command text for the query along with the dialect of the command text. In this case, use the LDAP dialect. You may also pass DBGUID_DBSQL, providing the query text is in that format.

hr = pICommandText->SetCommandText(DBGUID_LDAPDialect, wCmdString);

Do the Query

Next, tell Active Directory OLE DB provider to execute the statement. If successful, an IRowset interface is returned.

hr= pICommandText->Execute(NULL, 
    IID_IRowset,                 // Interface requested.
    NULL, 
    &cNumRows,                   // Number of rows returned.
    (IUnknown **) &pIRowset);    // IRowset interface.

Retrieve Column Information

An IRowset object is now available. Send a query for the IColumnsInfo interface to obtain the column information of the result set.

hr = pIRowset->QueryInterface(IID_IColumnsInfo, (void **) &pIColumnsInfo);
CheckHRESULT(hr,"");

Now, use the IColumnsInfo::GetColumnsInfo method to get the column information from the command object.

hr = pIColumnsInfo->GetColumnInfo( &cCol, &pDBColumnInfo, &pStringsBuffer );
 
CheckHRESULT(hr,"");
DWORD    dwText;

Display the name of each column in the result set. Delimit the output with tab characters.

wszDispBuffer[0]='\0';
for(ULONG nCount=0 ; nCount < cCol; nCount++)
{
    if (pDBColumnInfo[nCount].pwszName)
        wcscat(wszDispBuffer, pDBColumnInfo[nCount].pwszName);
    dwText = wcslen(wszDispBuffer);
    wszDispBuffer[dwText++] = L'\t';
    wszDispBuffer[dwText] = L'\0';
}
 
// NULL terminate the display buffer.
if (*wszDispBuffer)
    wszDispBuffer[wcslen(wszDispBuffer)-1]=L'\0';
 
_putws(wszDispBuffer);

Setup Column Bindings

Next, create column bindings for the result set. This code essentially tells the IAccessor (created in the following example) to return all column data as Unicode strings. This simplifies this example as no type coercion is necessary.

DWORD dwOffset = 0;
for (ULONG ulBind=0; ulBind < cCol; ulBind++)
{
    // Binding structure.
    rgBind[ulBind].dwPart      = DBPART_VALUE | DBPART_LENGTH |
                                  DBPART_STATUS;
    rgBind[ulBind].eParamIO    = DBPARAMIO_NOTPARAM;
    rgBind[ulBind].iOrdinal    = pDBColumnInfo[ulBind].iOrdinal;
    rgBind[ulBind].wType       = DBTYPE_WSTR;
    rgBind[ulBind].pTypeInfo   = NULL;
    rgBind[ulBind].obValue     = dwOffset + offsetof(DBCOLUMNDATA,bData);
    rgBind[ulBind].obLength    = dwOffset + offsetof(DBCOLUMNDATA,dwLength);
    rgBind[ulBind].obStatus    = dwOffset + offsetof(DBCOLUMNDATA,wStatus);
    rgBind[ulBind].cbMaxLen    = MAXROWLEN;
    rgBind[ulBind].pObject     = NULL;
    rgBind[ulBind].pBindExt    = NULL;
    rgBind[ulBind].dwFlags     = 0;
    rgBind[ulBind].dwMemOwner  = DBMEMOWNER_CLIENTOWNED;
    rgBind[ulBind].bPrecision  = 0;
    rgBind[ulBind].bScale      = 0;
 
    dwOffset += rgBind[ulBind].cbMaxLen + offsetof( DBCOLUMNDATA, bData );
    dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
    //iBind++;
} 
cMaxRowSize = dwOffset;

Create the Accessor

Use the IAccessor interface to get an accessor for our bindings from the rowset. Then call the IAccessor::CreateAccessor method, which returns an array of accessor handles. This is an array of handles to the rows in the result set.

hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );
 
CheckHRESULT(hr,"");
 
hr = pIAccessor->CreateAccessor(
                    DBACCESSOR_ROWDATA, 
                    ulBind, 
                    rgBind, 
                    0, 
                    &hAccessor,
                    NULL );

Retrieve the Data One Row At a Time

First, allocate a block of memory (pRowData) which will hold one row of the result set. Then call the IRowset::GetNextRows method to pass in the number of rows desired, and pointers to the return row data (pRowData), and the number of rows returned (cRowsObtained). When the row data is available, call the PrintRowData function.

BYTE*    pRowData = NULL;            // Memory for data.
 
// Create a buffer for row data, big enough to hold the biggest row
pRowData = (BYTE *) malloc( cMaxRowSize );
 
ULONG     cRowsObtained;            // Number of rows obtained.
HROW     rghRows[NUMROWS_CHUNK];    // Row handles.
 
// Process all the rows, one by one, NUMROWS_CHUNK rows at a time.
do 
{
    hr = pIRowset->GetNextRows(
        0,                         // cbChapter
        0,                         // cRowsToSkip
        NUMROWS_CHUNK,             // cRowsDesired
        &cRowsObtained,            // cRowsObtained
        &pRows );                  // filled in w/ row handles
 
    // If you are NOT done retrieving rows
    if ( cRowsObtained )
    {
        // Loop over rows obtained, getting data for each
        for (ULONG ulRow=0; ulRow < cRowsObtained; ulRow++ )
        {
            // Actually retrieves the row.
hr = pIRowset->GetData(
                pRows[ulRow],      // Row Handle
                hAccessor,         // Handle to Accessor
                pRowData );        // Pointer to Row Data
 
            // Print to the Screen.
            PrintRowData( rgBind,pDBColumnInfo, ulBind, pRowData );
        }
        // Release the row handles.
        hr = pIRowset->ReleaseRows( cRowsObtained, rghRows, 0, 0, 0);
    }
} while (cRowsObtained);

PrintRowData Function

This function takes the column binding structure (DBBINDING), the column information structure (DBCOLUMNINFO), the number of columns bound, and a BYTE pointer to the row data. It retrieves the column offsets in the row data, retrieves the column data for that column, and displays the data to the console window in tab delimited format. The dwStatus attribute of the DBCOLUMN structure is read to assert that the OLE DB provider was able to successfully retrieve the column data. Recall that when you initially bound the columns, it was specified that all column data shall come back as Unicode strings.

void PrintRowData
(
DBBINDING*     rgBind,           // DBBINDING Structure 
DBCOLUMNINFO*  pDBColumnInfo,    // DBCOLUMNINFO Structure
ULONG          ulNumColsBind,    // Number of columns that are bound to
BYTE*          pData             // Row data retrieved with 
// IRowset::GetData()
 )
 
{
    DWORD      dwOutIndex =0;    // Index for tracking the output string
    ULONG      ulCurrentCol;     // Index for the current column
    DBCOLUMNDATA*    pColumn;    // Data structure
 
    // Print each column that is bound to.
    for (ulCurrentCol=0, wszDispBuffer[0]='\0'; 
ulCurrentCol < ulNumColsBind; ulCurrentCol++)
    {
        // Grab a pointer to the column info -
        / found right behind the status offset 
        // for this column in the data buffer.
        pColumn = (DBCOLUMNDATA *) (pData + rgBind[ulCurrentCol].obStatus);
 
        if (pDBColumnInfo[ulCurrentCol].pwszName)
        {
            switch (pColumn->wStatus)
            {
                case DBSTATUS_S_ISNULL:
                    wcscat(wszDispBuffer, L"<NULL>");
                    break;
                case DBSTATUS_S_OK:
                case DBSTATUS_S_TRUNCATED:
                {
                    if ((LPWSTR)pColumn->bData)
                        wcscat (wszDispBuffer,
(LPWSTR)pColumn->bData);
                    else
                        wcscat (wszDispBuffer,L"null");
                    break;
                }
                case DBSTATUS_E_CANTCONVERTVALUE:
wcscat(wszDispBuffer, L"<can't convert value>");
                    break;
                default:
                    wcscat(wszDispBuffer,L"<unknown>");
                    break;
            }
 
            dwOutIndex = wcslen(wszDispBuffer);
            wszDispBuffer[dwOutIndex++] = L'\t';
            wszDispBuffer[dwOutIndex] = L'\0';
        }
    }
    // The last tab goes away.
    wszDispBuffer[--dwOutIndex] = L'\0';
    
    // Print the row to the screen.
_putws(wszDispBuffer);
 
}