Active Directory Service Interfaces - Getting Started

You should set up your ADSI development environment before we start. ADSI is very easy and fun to program. This page gives you a quick start on ADSI programming. For more advanced topics and provider-specific information, see the documentation and samples in the Platform SDK.

How do I:

- Connect/bind to a directory object
  First, you must bind to a directory object before any other operations, such as searching, reading and modifying attributes, or enumerating are allowed. You can connect or bind to a directory object remotely.

- Specify alternate credentials
  In addition to the currently logged on user, ADSI allows you to specify other alternate credentials.

- Enumerate children in a container
  A directory is normally a hierarchical model. It may have children which are also parents of other children.

- Read an attribute on an object
  An object may contain one or more attributes which can have a single value or multiple values.

- Write an attribute on an object
  You may modify attributes or write a new value.

- Create an object
  An object can be created for a given parent.

- Delete an object
  An object can be deleted for a given parent.

- Rename an object
  An object can be renamed for a given parent.

- Move an object
  An object can be moved from a given parent to a new destination parent.

- Get a child object
  You can bind to an object for a given parent.

- Get a parent of an object
  You can bind to the parent object.

- Search for a set of objects
  You can specify criteria for searching a set of objects.

- Filtering a set of objects
  Another easy way to search is to use filtering. This is a subset of search capabilities.

- Read Schema
  Each ADSI provider has a schema which defines the classes, attributes, and syntax.

What's Next?

Now that you are managing basic ADSI operations, you can continue your quest to set up a fully functioning directory. To focus on tasks you want to accomplish for the directory you're connecting to, visit the provider's pages (WinNT for NT 4.0, LDAP for Active Directory, SiteServer, Exchange and other LDAP directories, and more).

Connecting/binding to a directory object

Before you bind to a directory object, you must know the ADsPath. For example, in the Windows NT 4.0 directory, the path can be WinNT://domainName/object. For Windows 2000 Active Directory, you can bind to it as LDAP://DC=MyCompany, DC=COM. For Exchange Server, you should know the Exchange Server name and its organization, for example, LDAP://exchSrv01/O=Microsoft. For more information about each provider, visit the provider pages.

To bind to an object, use the GetObject function for Visual Basic and the ADsGetObject function for Visual C++. These functions run in the context of the currently logged on user.

'---Visual Basic Sample: Binding to JSmith user in ARCADIABAY domain
Set obj = GetObject("WinNT://ARCADIABAY/JSmith,user")
'Source code can be found in the \samples\start\Binding\vb directory.

// Visual C++ Sample: Binding to JSmith user in ARCADIABAY domain
HRESULT hr;
IADs *pADs;
hr = ADsGetObject( L"WinNT://ARCADIABAY/JSmith,user", IID_IADs, (void**) &pADs );
//...
pADs->Release(); // Do not forget to release when you're done.

//Source code can be found in the \samples\start\Binding\vc directory.

Back to top

Specifying alternate credentials

Sometimes, you would like to bind to another object that has a different set of credentials. This can be accomplished using the IADsOpenDSObject interface, or the ADsOpenObject function for Visual C++.

'---- Visual Basic Sample: Binding to the JSmith user in the ArcadiaBay domain with an alternate credential

Set dso = GetObject("WinNT:")
Set usr = dso.OpenDSObject("WinNT://ARCADIABAY/JSmith,user", "Administrator", "secret", ADS_SECURE_AUTHENTICATION)
'Source code can be found in the \samples\start\Binding\vb directory.

//---- Visual C++ Sample: Binding to the JSmith user in the ArcadiaBay domain with an alternate credential
IADs *pADs;
HRESULT hr;
hr = ADsOpenObject(L"WinNT://ARCADIABAY/JSmith,user", L"Administrator", L"secret", ADS_SECURE_AUTHENTICATION, IID_IADs, (void**) &pADs );
//...
// Make sure that you release it after use.
pADs->Release();

//Source code can be found in the \samples\start\Binding\vc directory.

Back to top

Enumerating children in a container

Once you bind to an object which is a container, you'll be able to enumerate its children using a normal For...each statement in Visual Basic, or using the ADsBuildEnumerator function for Visual C++. Note that only immediate children are enumerated using this call. If you want to list all of the objects of a subtree, you must use the Search function.

' Visual Basic Sample: Enumerating children for a given container
Set cont = GetObject("WinNT://INDEPENDENCE")
For Each obj In cont
Debug.Print obj.Name & " (" & obj.Class & ")"
Next
'Source code can be found in the \samples\start\Enum\vb directory.

// Visual C++ Sample: Enumerating children for a given container
////////////////////////////////
// Bind to a domain object
////////////////////////////////
hr = ADsGetObject(L"WinNT://INDEPENDENCE", IID_IADsContainer, (void**) &pCont );
if ( !SUCCEEDED(hr) )
{
return hr;
}
/////////////////////////////////
// Enumerate
/////////////////////////////////
IEnumVARIANT *pEnum = NULL;
hr = ADsBuildEnumerator( pCont, &pEnum );
if ( SUCCEEDED(hr) )
{
VARIANT var;
ULONG lFetch;
IADs *pChild=NULL;
VariantInit(&var);
while( SUCCEEDED(ADsEnumerateNext( pEnum, 1, &var, &lFetch )) && lFetch == 1 )
{
hr = V_DISPATCH(&var)->QueryInterface( IID_IADs, (void**) &pChild );
if ( SUCCEEDED(hr) )
{
BSTR bstrName;
BSTR bstrClass;
// Get more information on the child classes
pChild->get_Name(&bstrName);
pChild->get_Class(&bstrClass);
printf("%S\t(%S)\n", bstrName, bstrClass );
// Clean-up
SysFreeString(bstrName);
SysFreeString(bstrClass);
pChild->Release();
}
VariantClear(&var);
}
}

// Do not forget to free up the enumerator using ADsFreeEnumerator.
if ( pEnum )
{
ADsFreeEnumerator( pEnum );
}
pCont->Release();

// Source code can be found in the \samples\start\Enum\vc directory.

Back to top

Reading an attribute value on an object

To read an attribute, you can use the IADs::Get method for both Visual Basic and Visual C++. If you are a Visual C++ programmer, you can also use the IDirectoryObject::GetObjectAttributes method. Note that not all providers support the IDirectoryObject interface.

'--- Visual Basic Sample: Getting attributes with single and multiple values

Set usr = GetObject("WinNT://INDEPENDENCE/Administrator,user")
'--- Reading an attribute with a single value
Debug.Print usr.Name
Debug.Print usr.FullName
'Or you can also use generic Get
Debug.Print usr.Get("FullName")
'--- Reading attributes with multiple values
sid = usr.Get("objectSID")
For Each sidByte In sid
Debug.Print Hex(sidByte)
Next
'Or you can also use LBound and UBound for an attribute with multiple values.
For i = LBound(sid) To UBound(sid)
Debug.Print Hex(sid(i))
Next i

'Source code can be found in the \samples\start\Read\vb directory.

'--- Visual C++ Sample: Getting attributes with single and multiple values

HRESULT hr;
IADs *pUsr=NULL;
CoInitialize(NULL);
////////////////////////////////////
// Bind to a directory object
////////////////////////////////////
hr = ADsGetObject(L"WinNT://INDEPENDENCE/Administrator,user", IID_IADs, (void**) &pUsr );
if ( !SUCCEEDED(hr) )
{
return hr;
}
//////////////////////////////////////////
// Get an attribute with a single value
//////////////////////////////////////////
VARIANT var;
VariantInit(&var);
hr = pUsr->Get(L"FullName", &var );
if ( SUCCEEDED(hr) )
{
printf("FullName: %S\n", V_BSTR(&var) );
VariantClear(&var);
}
if ( pUsr )
{
pUsr->Release();
}
////////////////////////////////////////////////////////////////////
// Get an attribute with a multiple value from a service object
////////////////////////////////////////////////////////////////////
IADs *pSvc = NULL;
hr = ADsGetObject(L"WinNT://INDEPENDENCE/ANDYHAR11/Browser,service", IID_IADs, (void**) &pSvc );
if ( !SUCCEEDED(hr) )
{
return hr;
}
hr = pSvc->Get(L"Dependencies", &var );
if ( SUCCEEDED(hr) )
{
LONG lstart, lend;
SAFEARRAY *sa = V_ARRAY( &var );
VARIANT varItem;
// Get the lower and upper bound
hr = SafeArrayGetLBound( sa, 1, &lstart );
hr = SafeArrayGetUBound( sa, 1, &lend );
// Now iterate and print the content
VariantInit(&varItem);
printf("Getting service dependencies using IADs :\n");
for ( long idx=lstart; idx < lend; idx++ )
{
hr = SafeArrayGetElement( sa, &idx, &varItem );
printf("%S ", V_BSTR(&varItem));
VariantClear(&varItem);
}
printf("\n");
VariantClear(&var);
}
// Clean-up
if ( pSvc )
{
pSvc->Release();
}
//////////////////////////////////////////////////////////////////////////
// Using IDirectoryObject to get an attribute with multiple values
// Note: NOT all providers support this interface.
/////////////////////////////////////////////////////////////////////////
IDirectoryObject *pDirObject=NULL;
ADS_ATTR_INFO *pAttrInfo=NULL;
DWORD dwReturn;
LPWSTR pAttrNames[]={L"objectClass" };
DWORD dwNumAttr=sizeof(pAttrNames)/sizeof(LPWSTR);
hr = ADsGetObject(L"LDAP://CN=Administrator,CN=Users,DC=windows2000,DC=nttest,DC=microsoft,DC=com",
IID_IDirectoryObject,
(void**) &pDirObject );
if ( !SUCCEEDED(hr) )
{
return hr;
}
// Now get the attribute
hr = pDirObject->GetObjectAttributes( pAttrNames,
dwNumAttr,
&pAttrInfo,
&dwReturn );
if ( SUCCEEDED(hr) )
{
printf("Getting the objectClass multivalue attribute using IDirectoryObject :\n");
for (DWORD val=0; val < pAttrInfo->dwNumValues; val++, pAttrInfo->pADsValues++)
{
printf(" %S\n", pAttrInfo->pADsValues->CaseIgnoreString);
}
FreeADsMem(pAttrInfo); // the pAttrInfo must be freed using FreeADsMem
}
//Clean-up
pDirObject->Release();
CoUninitialize();

'Source code can be found in the \samples\start\Read\vc directory.

Back to top

Writing an attribute on an object

You can modify an attribute using the IADs::Put and IADs::PutEx methods. For Visual C++, you can also use the IDirectoryObject::SetObjectAttributes method.

'--- Visual Basic Sample: Setting attributes with single and multiple values

'Bind to a user
Set usr = GetObject("LDAP://CN=Jane Johnson,OU=DSys,DC=windows2000,DC=nttest,DC=microsoft,DC=com")
'Modify single attributes
usr.Put "givenName", "Jane"
usr.Put "sn", "Johnson"
'Modify multivalue attributes
usr.Put "otherTelephone", Array("425 844 1234", "425 924 4321")
'Commit to the directory
usr.SetInfo

'Source code can be found in the \samples\start\Write\vb directory.

//--- Visual C++ Sample: Setting attributes with single and multiple values

HRESULT hr;
IADs *pADs=NULL;
LPWSTR pszADsPath = L"LDAP://CN=Jane Johnson,OU=DSys,DC=windows2000,DC=nttest,DC=microsoft,DC=com";
CoInitialize(NULL);
//////////////////////////////////////
// Modifying attributes using IADs
//////////////////////////////////////
hr = ADsGetObject(pszADsPath,
IID_IADs,
(void**) &pADs );
if (!SUCCEEDED(hr) )
{
return hr;
}
VARIANT var;
// We omit checking the result for brevity.
// First Name
VariantInit(&var);
V_BSTR(&var) = SysAllocString(L"Jane");
V_VT(&var) = VT_BSTR;
hr = pADs->Put( L"givenName", var );
// Last Name
VariantClear(&var);
V_BSTR(&var) = SysAllocString(L"Johnson");
V_VT(&var) = VT_BSTR;
hr = pADs->Put( L"givenName", var );
VariantClear(&var);
// Other Telephones
LPWSTR pszPhones[] = { L"425 844 1234", L"425 924 4321" };
DWORD dwNumber = sizeof( pszPhones ) /sizeof(LPWSTR);
hr = ADsBuildVarArrayStr( pszPhones, dwNumber, &var );
hr = pADs->Put( L"otherTelephone", var );
VariantClear(&var);
hr = pADs->SetInfo();
pADs->Release();
if (!SUCCEEDED(hr) )
{
return hr;
}
/////////////////////////////////////////////////////
// Alternatively, you can use IDirectoryObject
/////////////////////////////////////////////////////
IDirectoryObject *pDir=NULL;
hr = ADsGetObject(pszADsPath,
IID_IDirectoryObject,
(void**) &pDir );
if ( !SUCCEEDED(hr) )
{
return hr;
}
DWORD dwReturn;
ADSVALUE snValue;
ADSVALUE fNameValue;
ADSVALUE phoneValue[2];
ADS_ATTR_INFO attrInfo[] = {
{L"givenName",ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING,&snValue,1},
{L"sn", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING,&fNameValue,1 },
{L"otherTelephone", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, phoneValue,2 }
};
DWORD dwAttrs = sizeof(attrInfo)/sizeof(ADS_ATTR_INFO);
///// First Name ///////////
fNameValue.dwType=ADSTYPE_CASE_IGNORE_STRING;
fNameValue.CaseIgnoreString = L"Johnson";
///// Last Name ///////////
snValue.dwType= ADSTYPE_CASE_IGNORE_STRING;
snValue.CaseIgnoreString = L"Johnson";
///// Other Telephone ///////////
phoneValue[0].dwType = ADSTYPE_CASE_IGNORE_STRING;
phoneValue[0].CaseIgnoreString = L"425 844 1234";
phoneValue[1].dwType = ADSTYPE_CASE_IGNORE_STRING;
phoneValue[1].CaseIgnoreString = L"425 924 4321";
hr = pDir->SetObjectAttributes(attrInfo, dwAttrs, &dwReturn);
pDir->Release();
if ( !SUCCEEDED(hr) )
{
return hr;
}
CoUninitialize();
return 0;

//Source code can be found in the \samples\start\Write\vc directory.

Back to top

Creating an object

To create an object, you can use the IADsContainer::Create method for both Visaul Basic and Visual C++. You can also use the IDirectoryObject::CreateDSObject method.

'-- Visual Basic Sample: Creating a user object

Set cont = GetObject("WinNT://INDEPENDENCE")
'---- Creating a user
Set usr = cont.Create("user", "JohnD")
usr.FullName = "John Doe"
usr.SetInfo

'Source code can be found in the \samples\start\Create\vb directory.

//-- Visual C++ Sample: Creating a user object

HRESULT hr;
IADsContainer *pCont = NULL;
CoInitialize(NULL);
hr = ADsGetObject( L"WinNT://INDEPENDENCE", IID_IADsContainer, (void**) &pCont );
if (!SUCCEEDED(hr) )
{
return hr;
}
IADs *pADs=NULL;
IDispatch *pDisp=NULL;
hr = pCont->Create(L"user", L"AliceW", &pDisp );
pCont->Release();
if( !SUCCEEDED(hr) )
{
return hr;
}
hr = pDisp->QueryInterface( IID_IADs, (void**) &pADs );
pDisp->Release();
if ( !SUCCEEDED(hr) )
{
return 0;
}
pADs->SetInfo(); // Commit
pADs->Release(); // Release

///////////////////////////////////////////////////////////
// Use IDirectoryObject to create an object.
////////////////////////////////////////////////////////////
IDirectoryObject *pDirObject=NULL;
ADSVALUE sAMValue;
ADSVALUE uPNValue;
ADSVALUE classValue;
ADS_ATTR_INFO attrInfo[] =
{
{ L"objectClass", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &classValue, 1 },
{L"sAMAccountName", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &sAMValue, 1},
{L"userPrincipalName", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &uPNValue, 1},
};
DWORD dwAttrs = sizeof(attrInfo)/sizeof(ADS_ATTR_INFO);
classValue.dwType = ADSTYPE_CASE_IGNORE_STRING;
classValue.CaseIgnoreString = L"user";
sAMValue.dwType=ADSTYPE_CASE_IGNORE_STRING;
sAMValue.CaseIgnoreString = L"mikes";
uPNValue.dwType=ADSTYPE_CASE_IGNORE_STRING;
uPNValue.CaseIgnoreString = L"mikes@arcadiabay.com";
// Create this user in an organizational unit
hr = ADsGetObject(L"LDAP://OU=DSys,DC=windows2000,DC=nttest,DC=microsoft,DC=com",
IID_IDirectoryObject, (void**) &pDirObject );
if ( SUCCEEDED(hr) )
{
hr = pDirObject->CreateDSObject( L"CN=Mike Smith", attrInfo,
dwAttrs, &pDisp );
if ( SUCCEEDED(hr) )
{
pDisp->Release();
}
}
pDirObject->Release();
CoUninitialize();

'Source code can be found in the \samples\start\Create\vc directory.

Back to top

Deleting an object

You can use the IADsContainer::Delete method in Visual Basic or Visual C++. You can also use the IDirectoryObject::DeleteDSObject method to delete an object if you are using Visual C++.

'-- Visual Basic Sample: Deleting a user object
Set cont = GetObject("WinNT://INDEPENDENCE")
'---- Deleting a user
cont.Delete "user", "JohnD"
'Source code can be found in the \samples\start\Delete\vb directory.

//-- Visual C++ Sample: Deleting a user object using the IADsContainer::Delete and IDirectoryObject::DeleteDSObject methods

HRESULT hr;
IADsContainer *pCont=NULL;
CoInitialize(NULL);
hr = ADsGetObject(L"WinNT://INDEPENDENCE", IID_IADsContainer, (void**) &pCont);
if ( !SUCCEEDED(hr) )
{
return hr;
}
///////////////////////////////////////////////////////
// Using IADsContainer::Delete to delete a user
///////////////////////////////////////////////////////
hr = pCont->Delete(L"user", L"AliceW");
pCont->Release();
/////////////////////////////////////////////////////////////////////
// Using IDirectoryObject::DeleteDSObject to delete a user
/////////////////////////////////////////////////////////////////////
IDirectoryObject *pDirObject=NULL;
hr = ADsGetObject(L"LDAP://OU=DSys,DC=windows2000,DC=nttest,DC=microsoft,DC=com",
IID_IDirectoryObject, (void**) &pDirObject );
if ( SUCCEEDED(hr) )
{
hr = pDirObject->DeleteDSObject(L"CN=Mike Smith");
pDirObject->Release();
}
CoUninitialize();

'Source code can be found in the \samples\start\Delete\vc directory.

Back to top

Renaming an object

You can rename an object using the IADsContainer::MoveHere method. First, you must know the object's parent.

'-- Visual Basic Sample: Renaming a user object using the IADsContainer::MoveHere method
Set cont = GetObject("WinNT://INDEPENDENCE")
'---- Renaming a user and updating its attributes
Set usr = cont.MoveHere("WinNT://INDEPENDENCE/JSmith", "JJohnson")
usr.FullName = "Jane Johnson"
usr.SetInfo
'Source code can be found in the \samples\start\Rename\vb directory.

//-- Visual C++ Sample: Renaming a user object using the IADsContainer::MoveHere method

HRESULT hr;
IADsContainer *pCont=NULL;
IDispatch *pDisp=NULL;
CoInitialize(NULL);
hr = ADsGetObject(L"WinNT://INDEPENDENCE", IID_IADsContainer, (void**) &pCont);
if ( !SUCCEEDED(hr) )
{
return hr;
}
hr = pCont->MoveHere(L"WinNT://INDEPENDENCE/JSmith",L"JJohnson", &pDisp );
if ( SUCCEEDED(hr) )
{
pDisp->Release();
}
CoUninitialize();

'Source code can be found in the \samples\start\Rename\vc directory.

Back to top

Moving an object

To move an object, you must bind to the parent's destination first, then call the IADsContainer::MoveHere method. Note that you can move and rename and object at the same time.

'-- Visual Basic Sample: Moving user object using the IADsContainer::MoveHere method

'Bind to the parent object's destination.
Set cont = GetObject("LDAP://OU=DSys,DC=windows2000,DC=nttest,DC=microsoft,DC=com")
'---- Moving a user from one organization to another.
Set usr = cont.MoveHere("LDAP://CN=Mike Smith,OU=MCS,DC=windows2000,DC=nttest,DC=microsoft,DC=com", vbNullString)

'Source code can be found in the \samples\start\Move\vb directory.

//-- Visual C++ Sample: Moving a user object using the IADsContainer::MoveHere method

///////////////////////////////////////////////
// First, bind to the destination container.
///////////////////////////////////////////////
HRESULT hr;
IADsContainer *pCont=NULL;

CoInitialize(NULL);
hr = ADsGetObject(L"LDAP://OU=MCS,DC=windows2000,DC=nttest,DC=microsoft,DC=com",
IID_IADsContainer,
(void**) &pCont );
if ( !SUCCEEDED(hr) )
{
return hr;
}
///////////////////////////////////////////////////////
// Now, move the object to the bound container.
///////////////////////////////////////////////////////
IDispatch *pDisp=NULL;
hr = pCont->MoveHere(L"LDAP://CN=Mike Smith,OU=DSys,DC=windows2000,DC=nttest,DC=microsoft,DC=com", NULL, &pDisp );
pCont->Release();
if (SUCCEEDED(hr) )
{
// You may do another operation here, such as updating attributes.
pDisp->Release();
}
CoUninitialize();

'Source code can be found in the \samples\start\Move\vc directory.

Back to top

Getting a child object

In ADSI, a container object exposes an IADsContainer interface. The IADsContainer interface has the IADsContainer::GetObject method so that you can bind directly to a child object. Note that the object returned by the IADsContainer::GetObject method has the same security context as the object on which the method was called. This means that if you have pointer to a container object and you know the relative path to a child object, you can avoid having to pass credentials again when binding to the child object. This may be useful when your application is binding using alternate credentials (that is, when it is not in the context of the logged on user).

'-- Visual Basic Sample: Getting a child object

'Bind to the parent container.
Set cont = GetObject("WinNT://INDEPENDENCE")
'---- Getting a child object from the container
Set usr = cont.GetObject("user", "JJohnson")
'Source code can be found in the \samples\start\Child\vb directory.

//Visual C++ Sample: Getting a child object

#define RETURN_ON_FAILURE(hr) if(!SUCCEEDED(hr)) return hr;

HRESULT hr;
CoInitialize(NULL);
IADsContainer *pCont=NULL;
hr = ADsGetObject(L"LDAP://DC=windows2000,DC=nttest,DC=microsoft,DC=com",
IID_IADsContainer,
(void**) &pCont );
RETURN_ON_FAILURE(hr);
////////////////////////////////////////////////////////////////////////////
// Get the child from the container.
// Note in the LDAP provider you can go down more than one level.
////////////////////////////////////////////////////////////////////////////
IDispatch *pDisp = NULL;
IADs *pADs = NULL;
hr = pCont->GetObject(L"user", L"CN=Mike Smith, OU=DSys", &pDisp );
pCont->Release();
RETURN_ON_FAILURE(hr);
hr = pDisp->QueryInterface( IID_IADs, (void**) &pADs );
pDisp->Release();
RETURN_ON_FAILURE(hr);
// Do something with pADs here.
pADs->Release();
CoUninitialize();

// Source code can be found in the \samples\start\Child\vc directory.

Back to top

Getting the parent of an object

In ADSI, a directory object is represented by an ADSI COM object, and every ADSI COM object exposes an IADs interface. The IADs interface has the IADs::get_Parent method so that you can get the ADsPath for the parent of the directory object. You can use that ADsPath to bind to the parent object.

'-- Visual Basic Sample: Getting a parent object

'Bind to an object.
Set usr = GetObject("WinNT://INDEPENDENCE/JJohnson,user")
'---- Getting a parent object using the IADs::get_Parent method.
Set cont = GetObject(usr.Parent)

' Source code can be found in the \samples\start\Parent\vb directory.

//-- Visual C++ Sample: Getting a parent object
HRESULT hr;
CoInitialize(NULL);
/////////////////////////////////////////
//Bind to an object
/////////////////////////////////////////
IADs *pADs = NULL;
hr = ADsGetObject(L"WinNT://INDEPENDENCE/JJohnson", IID_IADs, (void**) &pADs );
if (!SUCCEEDED(hr) )
{
return hr;
}
BSTR bstrParent;
IADs *pParent=NULL;
/////////////////////////////////
// Get the ADs Parent's Path
/////////////////////////////////
pADs->get_Parent(&bstrParent);
pADs->Release();
////////////////////////////////
// Bind to the Parent
////////////////////////////////
hr = ADsGetObject( bstrParent, IID_IADs, (void**) &pParent );
SysFreeString(bstrParent);
if (SUCCEEDED(hr) )
{
// Do something with pParent.
pParent->Release();
}
CoUninitialize();

// Source code can be found in the \samples\start\Parent\vc directory.

Back to top

Searching for a set of objects

You must use ADO to perform searches when using Visual Basic. If you use Visual C++, you can also use the IDirectorySearch interface.

'-- Visual Basic Sample: Searching for all groups
Dim con As New Connection, rs As New Recordset
Dim Com As New Command
'Open a Connection object
con.Provider = "ADsDSOObject"
con.Open "Active Directory Provider"
' Create a command object on this connection
Set Com.ActiveConnection = con
Com.CommandText = "select name from 'LDAP://DC=windows2000,DC=nttest,DC=microsoft,DC=com' where objectCategory='group' ORDER BY NAME"
Set rs = Com.Execute
'--------------------------------------
' Navigate the record set
'----------------------------------------
While Not rs.EOF
Debug.Print rs.Fields("Name")
rs.MoveNext
Wend

' Source code can be found in the \samples\start\Search\vb directory.

//-- Visual C++ Sample: Searching for all groups

HRESULT hr;
IDirectorySearch *pSearch;
CoInitialize(NULL);
///////////////////////////////////////////////////////
// Bind to the object, it serves as a base search
//////////////////////////////////////////////////////
hr = ADsGetObject(L"LDAP://DC=windows2000,DC=nttest,DC=microsoft,DC=com",
IID_IDirectorySearch,
(void**) &pSearch );
if ( !SUCCEEDED(hr) )
{
return hr;
}
///////////////////////////////////////
// Perform a subtree search
/////////////////////////////////////////
ADS_SEARCHPREF_INFO prefInfo[1];
prefInfo[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
prefInfo[0].vValue.dwType = ADSTYPE_INTEGER;
prefInfo[0].vValue.Integer = ADS_SCOPE_SUBTREE;
hr = pSearch->SetSearchPreference( prefInfo, 1);
////////////////////////////////////
// Prepare for attribute return
////////////////////////////////////
LPWSTR pszAttr[] = { L"Name"};
ADS_SEARCH_HANDLE hSearch;
DWORD dwCount= sizeof(pszAttr)/sizeof(LPWSTR);
//////////////////////////////////////////
// Search for all groups in a domain
//////////////////////////////////////////
hr = pSearch->ExecuteSearch(L"(objectCategory=Group)", pszAttr, dwCount, &hSearch );
if ( !SUCCEEDED(hr) )
{
pSearch->Release();
return hr;
}
//////////////////////////////////////////
// Now enumerate the result
//////////////////////////////////////////
ADS_SEARCH_COLUMN col;
while( pSearch->GetNextRow(hSearch) != S_ADS_NOMORE_ROWS )
{
// Get 'Name' attribute
hr = pSearch->GetColumn( hSearch, pszAttr[0], &col );
if ( SUCCEEDED(hr) )
{
printf("%S\n", col.pADsValues->CaseIgnoreString);
pSearch->FreeColumn( &col ); // You need to FreeColumn after use.
}
}
////////////////////
// Clean-up
////////////////////
pSearch->CloseSearchHandle(hSearch);
pSearch->Release();
CoUninitialize();

// Source code can be found in the \samples\start\Search\vc directory.

Back to top

Filtering a set of objects

'-- Visual Basic Sample: Getting groups and users

'Bind to a domain object
Set dom = GetObject("WinNT://INDEPENDENCE")
'Show all users and groups
dom.Filter = Array("user", "group")
For Each obj In dom
Debug.Print obj.Name & " (" & obj.Class & ")"
Next

' Source code can be found in the \samples\start\Filter\vb directory.

// -- Visual C++ Sample: Getting groups and users

HRESULT hr;
IADsContainer *pCont=NULL;
CoInitialize(NULL);
///////////////////////////////////
// Bind to the object
//////////////////////////////////
hr = ADsGetObject(L"WinNT://INDEPENDENCE", IID_IADsContainer, (void**) &pCont );
if ( !SUCCEEDED(hr) )
{
return hr;
}
///////////////////////////////////
// Build variant filter
//////////////////////////////////
LPWSTR pszFilter[] = { L"user", L"group" };
DWORD dwNumber = sizeof( pszFilter ) /sizeof(LPWSTR);
VARIANT var;
hr = ADsBuildVarArrayStr( pszFilter, dwNumber, &var );
if ( !SUCCEEDED(hr) )
{
pCont->Release();
return hr;
}
///////////////////////////////////
// Set the filter
//////////////////////////////////
hr = pCont->put_Filter(var);
VariantClear(&var);
if (!SUCCEEDED(hr) )
{
pCont->Release();
return hr;
}
////////////////////////////////////////////
// Enumerate the result
////////////////////////////////////////////
IEnumVARIANT *pEnum = NULL;
hr = ADsBuildEnumerator( pCont, &pEnum );
pCont->Release(); // This is no longer needed, since we have the enumerator already.
if ( SUCCEEDED(hr) )
{
VARIANT var;
ULONG lFetch;
IADs *pChild=NULL;
VariantInit(&var);
while( SUCCEEDED(ADsEnumerateNext( pEnum, 1, &var, &lFetch )) && lFetch == 1 )
{
hr = V_DISPATCH(&var)->QueryInterface( IID_IADs, (void**) &pChild );
if ( SUCCEEDED(hr) )
{
BSTR bstrName;
BSTR bstrClass;
// Get more information on the child classes
pChild->get_Name(&bstrName);
pChild->get_Class(&bstrClass);
printf("%S\t\t(%S)\n", bstrName, bstrClass );
// Clean-up
SysFreeString(bstrName);
SysFreeString(bstrClass);
pChild->Release();
}
VariantClear(&var);
}
}
CoUninitialize();

// Source code can be found in the \samples\start\Filter\vc directory.

Back to top

Reading the Schema

Most providers support the schema. The schema contains class and attribute definitions. ADSI abstracts the schema in the well known location, Provider://schema. Some providers may prefer to use the real schema for schema operations. Each object carries the schema location in which its class is defined. This information can be obtained using the IADs::get_Class method.

'-- Visual Basic Sample: Listing a schema information
'Bind to a schema container
Set Scm = GetObject("WinNT://INDEPENDENCE/Schema")
'Show all items in the schema container
For Each obj In Scm
Debug.Print obj.Name & " (" & obj.Class & ")"
Next
'You can also bind to an object and get the schema location
Set dom = GetObject("WinNT://INDEPENDENCE")
Debug.Print dom.Schema
Set Class = GetObject(dom.Schema)
'Mandatory attributes
For Each prop In Class.MandatoryProperties
Debug.Print prop
Next
'Optional attributes
For Each prop In Class.OptionalProperties
Debug.Print prop
Next

'Source code can be found in the \samples\start\Schema\vb directory.

//-- Visual C++ Sample: Listing schema information

IADsContainer *pSchema=NULL;
HRESULT hr;
CoInitialize(NULL);
hr = ADsGetObject(L"WinNT://INDEPENDENCE/Schema", IID_IADsContainer, (void**) &pSchema );
if ( !SUCCEEDED(hr) )
{
return hr;
}
////////////// Enumerate Schema objects ///////////////////////////////////
IEnumVARIANT *pEnum = NULL;
hr = ADsBuildEnumerator( pSchema, &pEnum );
pSchema->Release(); // This is no longer needed, since we have the enumerator already.
if ( SUCCEEDED(hr) )
{
VARIANT var;
ULONG lFetch;
IADs *pChild=NULL;
VariantInit(&var);
while( SUCCEEDED(ADsEnumerateNext( pEnum, 1, &var, &lFetch )) && lFetch == 1 )
{
hr = V_DISPATCH(&var)->QueryInterface( IID_IADs, (void**) &pChild );
if ( SUCCEEDED(hr) )
{
BSTR bstrName;
BSTR bstrClass;
// Get more information on the child classes
pChild->get_Name(&bstrName);
pChild->get_Class(&bstrClass);
printf("%S\t\t(%S)\n", bstrName, bstrClass );
// Clean-up
SysFreeString(bstrName);
SysFreeString(bstrClass);
pChild->Release();
}
VariantClear(&var);
}
}
CoUninitialize();

// Source code can be found in the \samples\start\Schema\vc directory.

Back to top