Active Directory Service Interfaces - Active Directory

For a start, please read the "Easy Access to Active Directory using ADSI" article.

For the latest update on Active Directory® programming, please visit: http://msdn.microsoft.com/developer/windows2000/adsi/actdirguide.asp
The primary purpose of this page is to give you a jump start on Active Directory programming. For more detailed programming topics, please visit the above link. For quick illustrations and better reading, the samples are in Visual Basic®. For Visual C++® programmers, the Active Directory Programmer's Guide provides plenty of C++ samples.

Requirements

- You must have Windows® 2000 Active Directory Server running.
- Your client can use Windows 2000 or higher, or Windows NT 4.0, Windows 95/98 with the DS Client.

How do I...

- Browse Active Directory

Bind

- Get to RootDSE
- Bind to a current domain
- Bind using alternate credentials
- Bind to the global catalog
- Bind to the Schema container
- Bind to the Configuration container
- Bind with GUID
- Bind with SID
- Who am I?

Get/Modify data

- Get domain mode
- List attributes that are replicated to the global catalog
- List indexed attributes
- Get UPN Suffixes
- Display the canonical name
- Create an organizational unit
- Create a user
- Create a group
- Delegate an organizational unit
- Create a computer account
- Remove a subtree
- Perform an ambiguous name resolution (ANR) search
- List attributes used in ANR

Browsing Active Directory

You can quickly browse the Active Directory using the ADSVW.EXE shipped with the SDK.

If your client is Windows NT 4.0 or Windows 95/Windows 98, then you'll need to know the server name or domain DNS name that hosts Active Directory.

- Run ADSVW.EXE.
- Select File | New.
- Type LDAP://yourServer.
- You can specify alternate credentials by checking Use Open Object, otherwise uncheck this option.

If your client is Windows 2000, and you're authenticated by Active Directory, you do not need to specify the server name.

Now you should be able to browse Active Directory.

Go to top.

Getting to RootDSE

The LDAP standard (RFC 2251) requires that all LDAP directories maintain a special entry, called the Root DS Entry, or Root DSE. This entry provides a set of standard operational attributes that the user can read to find out fundamental characteristics of the directory and the server. The Root DSE can also provide any number of vendor-specific attributes.

One of the standard operational attributes is "defaultNamingContext". This attribute contains the distinguished name (DN) of the root of the directory. In Windows 2000, this is the DN of the Domain container at the root of the current tree. By reading the defaultNamingContext attribute from the Root DSE, you can discover what domain you are logged in to at run time.

ADSI provides a special mechanism for binding to the root DSE: using the ADSpath "LDAP://RootDSE".

VB: set myObj = GetObject("LDAP://RootDSE")

VC: hr = AdsGetObject(L"LDAP://RootDSE", IID_IADs,(void **)&pDSObj);

Write a program that reads the defaultNamingContext attribute from the Root DSE to discover what domain you are logged into.
Source code can be found in \\samples\ActiveDir\RootDSE\VC

Go to top.

Binding with an Alternate Credential

ADSI binds to the directory using the credentials of the currently logged-in user. Sometimes you need to bind to a particular directory service using specific credentials, or using credentials that are different from those of the logged-in user.

ADSI provides an interface and method to provide this functionality. The namespace object supports the IADsOpenDSObject interface, which has a single method, OpenDSObject. OpenDSObject takes as arguments the ADSpath of the object or subtree to bind to, the username, the password, and the authentication method.

To obtain the IADsOpenDSObject interface, perform a default bind to "LDAP:".

VB: set dso = Getobject("LDAP:")
set myObj = dso.OpenDSObject( adsPath, userName, password, ADS_SECURE_AUTHENTICATION)

VC: hr = AdsGetObject(TEXT("LDAP:"),IID_IADsOpenDSObject,(void **)&pDSObj);

HRESULT OpenDsObject(LPTSTR Path, LPTSTR User,lpszPAssword,LONG Auth,Idispatch** ppDispatch)

Use ADS_SECURE_AUTHENTICATION for secured authentication (NTLM or Kerberos).

Write a program that discovers the current domain, then bind to it with explicit credentials.

Source code can be found in \\samples\ActiveDir\RootDSE\VC.

Go to top.

Binding to a Current Domain

You can bind to the current Active Directory domain using the entry found in the RootDSE. Domain information is shared only in that domain.

Example:
Set rootDSE = GetObject("LDAP://RootDSE")
Get domain = GetObject("LDAP://" & rootDSE.Get("defaultNamingContext") )

Go to top.

Binding to the Schema Container

You can bind to the Active Directory Schema container using the entry found in the RootDSE. Schema information is shared across the forest.
Example:
Set rootDSE = GetObject("LDAP://RootDSE")
Get schemaCont = GetObject("LDAP://" & rootDSE.Get("schemaNamingContext") )

Go to top.

Binding to the Configuration Container

You can bind the Active Directory Configuration Container using the entry found in the RootDSE. Data in the configuration partition is replicated across forest.
Example:
Set rootDSE = GetObject("LDAP://RootDSE")
Get schemaCont = GetObject("LDAP://" & rootDSE.Get("configurationNamingContext") )

Go to top.

Who am I?

NOTE: You must have a computer that runs Windows 2000 in order to execute this sample.
Win32 has always provided APIs that return the name of the current user and computer. In Windows 2000, the user and computer are represented by objects stored in Active Directory. Two new Win32 APIs are provided that return the distinguished name for the logged-on user and computer: GetUserNameEx and GetComputerObjectName (another new API, GetComputerNameEx, returns the DNS name of the computer).

> Long GetUserNameEx(long Nameform, LPTSTR buf, ULONG * buflen);

> Long GetComputerObjectName(long Nameform, LPTSTR buf, ULONG * buflen);

The version of ADSI that is shipped with Windows 2000 also provides a new interface, IADsADSystemInfo, which allows you to do this same task.

Go to top.

Binding with GUID
Binding to an object GUID has many advantages. An object's GUID is unique and never changes, even when the object is renamed or moved.

Example:

> Set obj = GetObject("LDAP://<GUID=08d0d12b43edd21196fc0080c7a2dc6b>")

or

> Set obj = GetObject("GC://<GUID=08d0d12b43edd21196fc0080c7a2dc6b>") 'for a forest-wide search

For a Visual C++ sample, click here.

Go to top.

Binding with SID

Every security principal object (such as users, groups, computers) has a SID. You can bind to that object based on the SID.

Example:

> Set obj = GetObject("LDAP://<SID=010500000000000515000000dcf4dc3b16c0ea32dbeb0c50f5010000>")

Go to top.

Binding to Global Catalog

You can bind to the global catalog using GC: as the provider.

To bind to the GC with a forest scope, you need to know a server name, or a domain DNS name in a forest. Optionally, you can manually enumerate the GC.

> Set gc = GetObject("GC://dc01") 'Using the server name

> Set gc = GetObject("GC://adomain.com") 'Using a domain name

----OR---- enumerate manually

> Set gcRoot = GetObject("GC:")

> For each gc in gcRoot 'There would be only one container on this root

> Next

> 'Now at this point, you can use the 'gc' variable to search in a forest scope.

To bind to the GC with a tree scope, you need to know the tree's distinguished name:

> Set gc = GetObject("GC://DC=FirstDomain, DC=COM")

To bind to GC with a domain scope, you need to know the domain's distinguished name:

> Set gc = GetObject("GC://DC=myDomain, DC=FirstDomain, DC=COM")

Go to top.

Getting the Domain Mode

Active Directory can operate in two modes. Native mode where all domain controllers are Windows 2000 servers, or mixed mode, where the backup domain controllers can be a mix of Windows NT 4.0 servers and Windows 2000 servers. The administrator must explicitly upgrade the domain mode. To find out the current domain mode, use the following code snippet:

> Set rootDSE = GetObject("LDAP://RootDSE")
> Set domain = GetObject("LDAP://" & rootDSE.Get("defaultNamingContext"))
> mode = domain.Get("nTMixedDomain")
> If (mode = 1) Then
> Debug.Print "Mixed Mode"
> Else
> Debug.Print "Native Mode"
> End If

Go to top.

Listing Attributes that are Replicated to GC

Only selected attributes are replicated to a GC. To find out which ones are replicated, search from the Schema container, then use either ADO or IDirectorySearch (for C++) with the following LDAP filter:

> (&(objectCategory=attributeSchema)(isMemberOfPartialAttributeSet=TRUE))

Go to top.

Listing Indexed Attributes

An indexed attribute is useful for quick searches. To find out all indexed attributes, you can bind to the Schema container using either ADO or IDirectorySearch (for C++) with the following LDAP filter.

> (&(objectCategory=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))

The indexed attribute is the attribute with the 0x00000001 bit set.

Go to top.

Getting UPN Suffixes

You can optionally choose UPN suffixes for your company. This list will appear on the Administrator Tool when composing a User Principal Name during user creation.

> Set rootDSE = GetObject("LDAP://RootDSE")
> Set partCont = GetObject("LDAP://CN=Partitions," & rootDSE.Get("configurationNamingContext"))
> suffixes = partCont.GetEx("UPNSuffixes")
> For Each upnSuffix In suffixes
> Debug.Print upnSuffix
> Next

Go to top.

Displaying the Canonical Name

It's recommended that you display a canonical name to the user instead of a distinguished name. A canonical name (or friendly name) is in the form of dnsDomainName/objectPath. For example, if the DN is CN=JSmith, OU=Marketing, OU=DSys, DC=ArcadiaBay, DC=Com, then its canonical name is arcadiayBay.com/dsys/marketing/jsmith.

Active Directory supports an operational attribute that returns a canonized name. The attribute name is 'canonicalName'. To obtain an operational attribute, you can either use IDirectoryObject/IDirectorySearch (for VC++), or IADs::GetInfo (for VB and VC++).

Example:

> Set o = GetObject("LDAP://CN=James Smith,OU=Marketing,OU=DSys,DC=ArcadiaBay,DC=com")
> o.GetInfoEx Array("canonicalName"), 0
> Debug.Print o.Get("canonicalName") 'It should print as arcadiabay.com/DSys/Marketing/James Smith

For Windows 2000, another option is to use IADsNameTranslate.

Go to top.

Creating an Organizational Unit

To create an organizational unit, you need to know the parent container's distinguished name.

> Set parentCont = GetObject("LDAP://OU=DSys,DC=ArcadiaBay,DC=com")
> Set ou = parentCont.Create("organizationalUnit", "OU=Marketing")
> ou.Description = "Distributed System Marketing"
> ou.SetInfo

To create an organizational unit in a current domain, you can use the following code:

> Set rootDSE = GetObject("LDAP://RootDSE")
> Set dom = GetObject("LDAP://" & dom.Get("defaultNamingContext"))
> Set ou = dom.Create("organizationalUnit", "OU=Marketing")
> ou.Description = "Distributed System Marketing"
> ou.SetInfo

Go to top

Creating a User

A user normally lives in an organizational unit. To create a user, you'll need to supply the organizational unit and down-level user name, at the minimum.

> Set ou = GetObject("LDAP://OU=Marketing,OU=DSys,DC=adsidev,DC=nttest,DC=microsoft,DC=com")
> Set usr = ou.Create("user", "CN=James Smith")

> '---- Mandatory attributes----
> usr.Put "samAccountName", "jsmith"

> '---- Optional attributes, you may skip this----
> usr.Put "sn", "Smith"
> usr.Put "givenName", "James"
> usr.Put "userPrincipalName", "jsmith@arcadiaybay.com"
> usr.Put "telephoneNumber", "(555) 555 0111"
> usr.Put "title", "Marketing Administrator Dept"
> usr.SetInfo
> '--Now that the user is created, reset the user's password and
> '--enable its account.
> usr.SetPassword "secret***!"
> usr.AccountDisabled = False
> usr.SetInfo

Go to top.

Creating a Group

You can create a group or distribution list in Active Directory. Group can be either a domain local, global, or universal group. For more information about group, please follow the Active Directory Programmer's Guide.

> Set ou = GetObject("LDAP://OU=DSys,DC=ArcadiaBay,DC=com")
> Set grp = ou.Create("group", "CN=Distributed System Admin")
> '----Creating a domain local group----

> grp.Put "groupType", ADS_GROUP_TYPE_LOCAL_GROUP Or ADS_GROUP_TYPE_SECURITY_ENABLED
> grp.Put "samAccountName", "DSysAdmin"
> grp.SetInfo

> '----Adding a user to a group----

> grp.Add ("LDAP://CN=James Smith,OU=Marketing,OU=DSys,DC=ArcadiaBay,DC=com")

Go to top.

Delegating an Organizational Unit

Now that you have set up an organizational unit and created a user, you can delegate this organizational unit to the user. In our scenario, we will delegate the Marketing organizational unit to James Smith, so that he can create and delete users.

We will need to retrieve the security descriptor of that organizational unit and set the appropriate permission for James Smith.

> Set ou = GetObject("LDAP://OU=Marketing, OU=DSys,DC=ArcadiaBay,DC=com")
> Set sec = ou.Get("ntSecurityDescriptor")
> Set acl = sec.DiscretionaryAcl
> Set ace = CreateObject("AccessControlEntry") 'Or you can use Set ace = new ADsAccessControlEntry
> ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT 'Allow to
> ace.AccessMask = ADS_RIGHT_DS_CREATE_CHILD Or ADS_RIGHT_DS_DELETE_CHILD 'Create and delete user
> ace.ObjectType = "{BF967ABA-0DE6-11D0-A285-00AA003049E2}" 'User's GUID (schemaIDGuid)
> ace.AceFlags = ADS_ACEFLAG_INHERIT_ACE 'Prop down the ace
> ace.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT 'Tells what objectType is filled
> ace.Trustee = "ARCADIABAY\Jsmith" 'Who is the beneficiary of this ace
> acl.AddAce ace
> sec.DiscretionaryAcl = acl
> ou.Put "ntSecurityDescriptor", Array(sec)
> ou.SetInfo 'Commit to Active Directory
> Set ace = Nothing
> Set acl = Nothing
> Set sec = Nothing

Go to top.

Removing a Subtree

Use IADsDeleteOps to delete a subtree of Active Directory objects.

> Set ou = GetObject("LDAP://OU=Marketing,DC=ArcadiaBay,DC=com")
> ou.DeleteObject (0)

Go to top.

Ambiguous Name Resolution (ANR) Searching

The LDAP filter for ANR searching is (anr=yourSearch). For example (anr=John).

Go to top.

Listing All Attributes Used in ANR Searches

Bind to the Schema container and ADO or IDirectoryobject to perform a search. All attributes that have the 0x00000004 bit set on the attributeSchema object are included in the ANR query evaluations.

> (&(objectCategory=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=4))

Go to top.

Creating a Computer Account

'----CONSTANTS----

> Const UF_WORKSTATION_TRUST_ACCOUNT = &H1000
> Const UF_ACCOUNTDISABLE = &H2
> Const UF_PASSWD_NOTREQD = &H20
> Const ADS_GUID_COMPUTRS_CONTAINER = "aa312825768811d1aded00c04fd8d5cd"
> Const ADS_ACETYPE_ACCESS_ALLOWED = 0
> Const ADS_ACEFLAG_INHERIT_ACE = 2

'----PARAMETERS ----

lFlag = UF_WORKSTATION_TRUST_ACCOUNT Or UF_ACCOUNTDISABLE Or UF_PASSWD_NOTREQD
sComputer = "myMachine"
sUserOrGroup = "MYDOMAIN\MyGroup" 'Who can join this computer.

'----BUILD WELL-KNOWN GUID ADSPATH FOR COMPUTER CONTAINER----

Set rootDSE = GetObject("LDAP://RootDSE")
sPath = "LDAP://<WKGUID=" & ADS_GUID_COMPUTRS_CONTAINER
sPath = sPath + ","
sPath = sPath + rootDSE.Get("defaultNamingContext")
sPath = sPath + ">"

Set compCont = GetObject(sPath)

'Bind again to get the correct ADsPath
sPath = "LDAP://" & compCont.Get("distinguishedName")
Set compCont = GetObject(sPath)

'----CREATE A COMPUTER OBJECT----

> Set comp = compCont.Create("computer", "CN=" & sComputer)
> comp.Put "samAccountName", sComputer + "$"
> comp.Put "userAccountControl", lFlag
> comp.SetInfo

'----SET INITIAL PASSWORD----

> sPwd = sComputer
> sPwd = StrConv(sPwd, vbLowerCase)
> comp.SetPassword sPwd

'----SET SECURITY----

> Set sd = comp.Get("ntSecurityDescriptor")
> Set dacl = sd.DiscretionaryAcl

'----SET ACE----

> Set ace = CreateObject("AccessControlEntry")
> ace.AccessMask = -1 'Full Permission (Allowed)
> ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED
> ace.Trustee = sUserOrGroup

'----ACL----

> dacl.AddAce ace
> sd.DiscretionaryAcl = dacl

'----SD----

> comp.Put "ntSecurityDescriptor", Array(sd)
> comp.SetInfo

'----ENABLE THE ACCOUNT----

> comp.AccountDisabled = False
> comp.SetInfo

Go to top