Directory Services

Code Walkthrough: Using OLE DB to Access Active Directory

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

ADSI defines two GUIDs for dialects:

The following uses the LDAP dialect.

You may also pass DBGUID_DEFAULT as the dialect. In this case, ADSI attempts to use the SQL dialect first; if that fails, it retries using the LDAP dialect. For more information, see LDAP dialect and SQL dialect.

For more information about OLE DB, see the OLE DB Programmer's Guide.

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 data 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 of data.

Query String

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

LPCTSTR wCmdString =OLESTR( "<LDAP://coho.salmon.Fabrikam.com/cn=users,dc=coho,dc=salmon,dc=Fabrikam,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 returns an IDBInitialize interface. This interface is the root of the 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

To prepare to call the IDBProperties::SetProperties method, build an array of the properties for the OLE DB connection. The following code example 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 performed for 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

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 filled in a previous step.

// Set the 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 connects 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 can 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::GetColumnInfo method to get the column data 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 example notifies the IAccessor, created in the following example, to return 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 the 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) that 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 PrintRowData.

BYTE*	pRowData = NULL; 		// Memory for data.
 
// Create a buffer for row data, large enough to hold the largest 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 with row handles.
 
	// If all rows are not retrieved.
	if ( cRowsObtained )
	{
		// Loop over rows obtained, getting data for each.
		for (ULONG ulRow=0; ulRow < cRowsObtained; ulRow++ )
		{
			// Retrieve the row.
hr = pIRowset->GetData(
				pRows[ulRow], // Row Handle.
				hAccessor, 	 // Handle to Accessor.
				pRowData ); 	// Pointer to Row Data.
 
			// Print to 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 data 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 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++)
	{
		// Get 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"<cannot 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);
 
}