banner.gif (5982 bytes)

mslogo.gif (666 bytes)

router.gif (3874 bytes)

 
General

WinNT

Active Directory


GetInfoEx

When the first Get issue on an object, ADSI ,implicitly,brings all attributes which have 1) value 2) user has a read permission into ADSI cache. This is designed for optimizing network traffic, server load for most cases and ease of use- one big round trip is normally better than chatty traffics.  However, in some cases, you like to get only a small set of attributes. GetInfoEx does exactly this. It allows you to specify only attributes you're interested in.

Example,
Set o = GetObject("LDAP://CN=Jsmith, DC=ArcadiaBay, DC=COM")
o.GetInfoEx Array("sn","givenName","telephoneNumber"), 0
Debug.Print o.Get("sn")
Debug.Pritn o.Get("givenName")
 
Back to top

Filtering

Often time you would like to enumerate only certain types of class in a container. It's more efficient to filter first, instead of doing comparision during the enumeration.

Example, instead of doing this
'--- Print all users in a domain
Set cont = GetObject("WinNT://ARCADIABAY")
For each obj in cont
  if obj.Class = "User" then
     Debug.Print obj.Name
  End if 
Next

Instead, you should do this:

'--- Print all users in a domain
Set cont = GetObject("WinNT://ARCADIABAY")
con.Filter = Array("user")
For each usr in cont
     Debug.Print usr.Name
Next

Back to top

GetEx

If you don't if the attributes are single or multi value in advanced, GetEx comes handy. Regardless how many values in an attribute, you can rely on GetEx for value enumeration.

Example,
Set usr = GetObject("LDAP://CN=Jsmith, DC=ArcadiaBay, DC=COM")

phone = usr.Get("telephoneNumber") 'single value
while val in phone
   Debug.Print val
Next 

phone = usr.Get("otherTelephone")
while val in phone
  Debug.Print val
Next

Back to top

Property Caching

With IADsPropertyList, IADsPropertyEntry and IADsPropertyValue, you will be able to manipulate ADSI caches.

Example,

Set oObject = GetObject("LDAP://CN=Andrew Anderson,OU=Sales,DC=Antipodes,DC=com")
WScript.Echo "User: " & oObject.cn

oObject.GetInfo 'Load into ADSI's caches
WScript.Echo "Property Count: " & oObject.PropertyCount
For i = 0 to oObject.PropertyCount -1
  Set X = oObject.Item(i+1) 
  For Each Y In X.Values
       WScript.Echo "Attribute: " & X.Name & " " & " (" & CStr(aProperty(Y.ADsType+1)) & ")"
       Select Case Y.ADsType + 1
          Case ADSTYPE_DN_STRING
                  WScript.Echo "Value: " & Y.DNString
          Case ADSTYPE_CASE_EXACT_STRING
                 WScript.Echo "Value: " & Y.CaseExactString        
          Case ADSTYPE_CASE_IGNORE_STRING
                 WScript.Echo "Value: " & Y.CaseIgnoreString
          Case ADSTYPE_PRINTABLE_STRING
                 WScript.Echo "Value: " & Y.PrintableString
          Case ADSTYPE_NUMERIC_STRING
                 WScript.Echo "Value: " & Y.NumericString
          Case ADSTYPE_BOOLEAN
                 WScript.Echo "Value: " & Y.Boolean
          Case ADSTYPE_INTEGER
                 WScript.Echo "Value: " & Y.Integer
           //... and more here....   
   Next
Next

Back to top

WinNT: Binding

In WinNT, you will be able to specify the object's class as part of the ADsPath. This will speed up the bind in most cases. Examples: "WinNT://ARCADIABAY/jsmith,user" , "WinNT://ARCADIABAY/marketing,group

Back to top

 

WinNT: Communicating to a specific domain controller

If you specify bind to a domain or objects in a domain, ADSI, by default, communicates to Primary Domain Controller. In ADSI, there is a way, to select to a specific domain controller. Specify the computer name with ',computer' as part of ADsPath.
Example: "WinNT://bdc02,computer" 

Note: if the domain controller is a backup domain controller, all the write operations will fail.

Back to top


WinNT: Including backup domain controllers as part of selection

ADSI 2.5 introduces a new flag (ADS_READONLY_SERVER) for including back up domain controllers as part of selection criteria when bind to a domain. ADSI may select either PDC or BDC.  

Example,
Set dso = GetObject("WinNT:")
Set obj = dso.OpenDSObject("WinNT://ARCADIABAY/jsmith,user", "Administrator", "secret", ADS_READONLY_SERVER )

Note: if the domain controller is a backup domain controller, all the write operations will fail.

Back to top


Active Directory: Searching the entire domain tree

Although it's very possible, chasing referral is discouraged for a domain tree search. The client may have to connect n number of domain controllers before getting the complete result set. Global catalog is designed for enterprise, tree search. Since not all attributes are replicated to Global Catalog, you may want to rebind to the targeted object once you retreive its distinguished name.

example,
Set gc = GetObject("GC://DC=Central, DC=ArcadiaBay, DC=COM")
//... do ADO search here...

Back to top

Active Directory: Object Class vs Object Category

Object class is a multi-valued attribute. Active Directory does not index this attribute for many reasons. Active Directory introduces a similar concept - objectCategory, is an indexed, single value attributes. Include objectCategory to search a certain type of class.

Note: With the exception of (objectClass=*), you should always include objectCategory for efficient searches. 

Example,

-Find all users
(&(objectCategory=Person)(objectClass=user))

-Find all groups
(objectCategory=Group)

-Find all computers
(objectCategory=Computer)

-Find all object
(objectClass=*)

Back to top

LDAP: Fast Bind

When you request an LDAP bind using ADSI, one of things ADSI will do is to do a quick base search on that object itself. This is done for the following reasons: 1) to make sure the object exists 2) to get the object class so that ADSI will be able to load the appropriate ADSI extensions and interfaces. By specifying ADS_FAST_BIND, ADSI will skip this base search. This means that ADSI does not check if object's existence, and ADSI only supports IADs interface for this bound object. 

One scenario is updating attributes on objects found in a result set. Since the objects are included in the result set, they are highly likely exist in the directory. 

example, 
Set dso = GetObject("LDAP:")
... do Search....
while rs.EOF 
    adsPath = rs.Fields("ADsPath")
    Set obj = dso.OpenDSObject(adsPath, vbNullString, vbNullString, ADS_SECURE_AUTHENTICATION | ADS_FAST_BIND )
    rs.MoveNext
wend

Back to top

Active Directory: Operational Attributes

Active Directory defines several operational attributes. An operational attribute is an artifact attribute. When one sets an operational attribute value, it, normally, triggers some actions on the server. On the other hand, when one gets an operational attribute value, the value itself, usually, is calculated on the server.   Like a normal attribute, an operational attribute can be either multivalue or single value. The operational attribute may be in read-only, write-only or read and write mode.

Active Directory defines several operational attributes,for examples: canonicalName, allowedChildClassesEffective, allowedAttributes, possibleInferirors, lowestUncommitedUSN, and many more.

To retrieve or set an operational attribute value, you must use IDirectoryObject, or IADs::GetInfoEx

'--- Retrieve a canonical name (every object in Active Directory has a canonicalName)
Set obj = GetObject("LDAP://CN=Cert Publishers,CN=Users,DC=Arcadiabay,DC=com")
obj.GetInfoEx Array("canonicalName"), 0
Debug.Print obj.Get("canonicalName") '---it should be: arcadiabay.com/Users/Cert Publishers

//----- Find all allowed attributes for a given object in the current user security context  -------

    IIDirectoryObject *pObject;
   
    hr = ADsGetObject(L"LDAP://DC=activeds,DC=ntdev,DC=Microsoft,DC=Com",
        IID_IDirectoryObject, (void**) &pObject );

    if (!SUCCEEDED(hr) )
    {
        return hr;
    }

   
   
    LPWSTR szAttrs[] = { L"allowedAttributesEffective" };
    PADS_ATTR_INFO pAttrEntry;
    DWORD dwReturn;
    hr = pObject->GetObjectAttributes( szAttrs, 1, &pAttrEntry, &dwReturn );

    if ( !SUCCEEDED(hr) )
    {
        pObject->Release();
        return hr;
    }

    if ( pAttrEntry )
    {
        for( DWORD idx=0; idx < pAttrEntry->dwNumValues; idx++ )
        {
            printf("%ws\n", pAttrEntry->pADsValues[idx].CaseIgnoreString );
        }
        FreeADsMem( pAttrEntry );
    }

    pObject->Release();
    return hr;

Back to top

Active Directory: GUID searching

Every object in Active Directory carries GUID which guarantees to be unique. GUID never changes even the object is renamed or moved. ADSI support GUID binding in the form of  LDAP://<GUID=xxxxxx> or GC://<GUID=xxx>

For example of GUID binding please see Active Directory GUID Binding

Back to top

Active Directory: Listing Mandatory and Optional Attributes

Both mandatory and operational attributes can be obtained from IADsClass. To get the IADsClass interface, first you must know the class name of an object. Secondly, compose the ADSPath from the class name and schema path. Third, bind this object and ask for IADsClass. The MandatoryProperties and OptionalProperties attributes contain in this interface.

 

Dim x As IADsClass
Dim classNams As String
Dim provider As String

className = "user"
provider  = "LDAP://schema/"

Set x = GetObject( provider & className )

For Each attr In x.MandatoryProperties
    Debug.Print attr
Next

For Each attr In x.OptionalProperties
    Debug.Print attr
Next

Back to top

Active Directory: Specifying Page Size 

On subtree search, it's recommended to specify the page size. Even if you're not expecting to find many objects with the search. Paging allows the search to timeout in a re-startable way, so that you can resume the search where it left off. Without paging, the search will simply fail when it hits a time limit. Paging also promote scalability for both clients and servers. 

ADSI handles the page size transparently. No need additional programming required, except specifying the page size in the search preferences.


VB Example,
Command.Properties("Page Size") = 500

'--Execute and enumerate ...

VC Example,
ADS_SEARCHPREF_INFO prefInfo[ MAX_SEARCH_PREF ];

prefInfo[dwCountPref].dwSearchPref = ADS_SEARCHPREF_PAGESIZE; 
prefInfo[dwCountPref].vValue.dwType = ADSTYPE_INTEGER;
prefInfo[dwCountPref].vValue.Integer = m_nPageSize;
dwCountPref++;

hr = m_pSearch->SetSearchPreference( prefInfo, dwCountPref )

Back to top

Active Directory: Security

Active Directory Security covers in detail in the Active Directory Programmer's Guide
Two common scenarios are presented here. - Organizational Unit Delegation and Setting ACL on an attribute

Example 1 - Organizational Unit Delegation

'---- Allowing JSmith to create and delete users in the sales organization ----------

Dim ace As New AccessControlEntry
Dim secDesc As IADsSecurityDescriptor
Dim acl As IADsAccessControlList

Set x = GetObject("LDAP://OU=Sales,DC=Arcadiay,DC=com")
Set secDesc = x.Get("ntSecurityDescriptor")
Set acl = secDesc.DiscretionaryAcl

ace.ObjectType = "{BF967ABA-0DE6-11D0-A285-00AA003049E2}"  '--- This is the User Class' schemaID GUID
ace.AccessMask = ADS_RIGHT_DS_CREATE_CHILD Or ADS_RIGHT_DS_DELETE_CHILD
ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
ace.AceFlags = ADS_ACEFLAG_INHERIT_ACE
ace.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT
ace.Trustee = "ARCADIABAY\JSmith"

acl.AddAce ace
secDesc.DiscretionaryAcl = acl
x.Put "ntSecurityDescriptor", Array(secDesc)
x.SetInfo


Example 2 - Setting ACL on an attribute

‘-- Deny JSmith to write “Managed By” attributes
Dim ace As New AccessControlEntry
Dim secDesc As IADsSecurityDescriptor
Dim acl As IADsAccessControlList

Set x = GetObject("LDAP://OU=Sales,DC=Arcadiay,DC=com")
Set secDesc = x.Get("ntSecurityDescriptor")
Set acl = secDesc.DiscretionaryAcl

ace.ObjectType = "{0296C120-40DA-11D1-A9C0-0000F80367C1}” ‘Managed By
ace.AccessMask = ADS_RIGHT_DS_WRITE_PROP
ace.AceType = ADS_ACETYPE_ACCESS_DENIED_OBJECT
ace.AceFlags = ADS_ACEFLAG_INHERIT_ACE
ace.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT
ace.Trustee = "ARCADIABAY\JSmith"

acl.AddAce ace
secDesc.DiscretionaryAcl = acl
x.Put "ntSecurityDescriptor", Array(secDesc)
x.SetInfo

Back to top