Contact Info

Crumbtrail

ActiveXperts.com » Administration » ADSI

Active Directory Service Interfaces (ADSI) Benefits

ADSI Introduction

Active Directory Services Interface (ADSI) is a set of COM (Common Object Model) programming Interfaces. Like ODBC, ADSI provides common access to directories by adding a provider for each directory protocol type. Windows contains providers for:

Benefits of accessing directories with ADSI:


ADSI Classes

Adsi includes a comprehensive set of powerfull classes, to enable (remote) administration, including:


ADSI benefits

ADSI Architecture

ADSI objects are COM objects, which represent objects in an underlying directory service. Objects can be container objects (like Folders) or Leaf objects (like Files). Each object has a unique ADSI path - a provider name followed by an object path. ADSI provides an abstract schema which describes the type of objects and attributes supported by each provider. Objects are read into cache when GetInfo or GetObject are called. Changes reside in cached memory on the client until a SetInfo is issued. SetInfo writes data back to the underlying directory store.

LDAP provider and serverless binding

The preferred method for connecting to an object is to use serverless binding; this means that the server is not explicitly provided; the default domain controller is the source of the LDAP requests. If the requested operations cannot be serviced in the local domain, a referral to the correct server is generated when possible, and the closest server is given. A serverless path is of the form LDAP://object. To bind to the domain DNS object which is the root container of the domain naming context:

Set Odse = GetObject( "LDAP//DC=corp,DC=Microsoft,DC=com")

RootDse

The RootDse is a special LDAP object that exists on all LDAP v3 servers. With it you can write scripts that are independent of the domain or enterprise on whih they are run:

Set Odse = GetObject( "LDAP://RootDse" )

For example, to reference an object in the current domain you can read the value for DefaultNamingContext to determine the current domain:

' Get path to the configuration naming context.
Set RootDse = GetObject( "LDAP//RootDse" )
Path = "LDAP://" & RootDse.get( "DefaultNamingContext" )

Domain-based information such as users, groups, and computers resides in the domain naming context. Enterprise configuration information such as sites and subnets can be found in the Configuration Naming context. You can learn the path to naming contexts by examining ConfigurationNamingContext and SchemaNamingContext.

Set Odse = GetObject( "LDAP//servername/RootDse" )

Using the Global Catalog

A global catalog (GC) server is a domain controller that contains a partial read-only replica of every object in every naming context. The replica is used to quickly search the enterprise for an object. The GC contains all objects from all naming contexts, but it is partial in that it contains only attributes designated for replication to the GC. The GC is accessed using port 3268 or by the GC provider as alias. In ADSI any reference to the GC is mapped to the LDAP provider on port 3268. Some of the common uses for searching the GC are:

Reading an object

To read an object you must first use GetObject to bind to it:

Set objUser = GetObject( "LDAP://CN=wjohnson;CN=Users;DC=klm,DC=com" )

This fills the object cache in the client's memory with the object's attributes. You can access each attribute by it's name:

WScript.Echo objUser.givenName & " " & objUser.sn & " " & objUser.mail
Set objUser = Nothing

Updating an object

To update attributes on an existing object, you have to first bind to the object with GetObject, set each attribute's value (to update the values in the local object cache on the client), then issue a SetInfo to write the changes back to the directory. This example sets the mail address and the user's last name on user account:

Set objUser  = GetObject( "LDAP://CN=wjohnson,CN=User,DC=klm,DC=com" )
objUser.mail = "wjohnson@klm.com"
objUser.sn = "wjohnson"
objUser.SetInfo
Set objUser = Nothing

Enumerating a container

To enumerate a container such as on OU, first bind to the OU and then use a loop to enumerate the container's object. To list all objects in the Users container:

Set ou = GetObject( "LDAP://CN=Users,DC=klm,DC=com" )
For each obj in ou
    WScript.Echo obj.Name
Next

Searching

OLE DB provides a common way to query for information in a database-like way. The ADSI OLE DB provider allows you to use ActiveX Data Object (ADOs) to search the Active Directory. The provider is read-only, so if you need to modify an object after you search for it, use GetObject with the AdsPath

To search the Active Directory you must first create the ADO connection and command objects:

' list all objects in the domain naming context
Dim con, rs, Com
Set con = WScript.CreateObject( "ADODB.Connection" )
Set com = WScript.CreateObject( "ADODB.Command" )

Next, open the ADO provider:

' Open a connection object
con.Provider = "ADsDSObject"
con.Open "Active Directory Provider"

Then set the command object to use the current connection object, and build the query using either a simple SQL format or the LDAP search filter format (used in the example below). The query specifies the search's starting path and a filter for matching (the sample below looks for any type of object). Then list the attributes you want the query to return. Finally, specify the depth of the search: subtree for a deep search, base for a single object, or one level for searching a container.

Set Com.ActiveConnection = con
Com.CommandText = "<GC://DC=hq,DC=klm,DC=com>;(objectClass=*);adspath;subtree"

' Set the preferences for Search

Com.Properties( "Page Size" ) = 512
Com.Properties( "TimeOut" ) = 30 ' seconds

When you execute the query, the results are retuned in a recordset. Loop through the recordset and print out the value, then move on to the next record.

While Not rs.EOF
    WScript.Echo rs.Fields( "AdsPath").Value
    Rs.MoveNext
Wend

Managing Domain Information

Extracting Computer Information

Network Administrators have always wanted an easy way to get a list of network workstations along with operating system and service pack information. You can now do this by using new attributes on Windows computer accounts to identify the computer's current status. The computer object is now automatically updated with information (from the netlogon service during secure channel setup) about the client's operating system, operating system version, and service pack level.

You can identify unused or possibly inactive computer accounts; accounts that have never been used do not have the operating system and version attributes set. If the whenChanged attribute is more than a month old, the computer probably is not active on a network making periodic password changes. The whenChanged attribute is a non-replicated attribute which means it is calculated on each DC. The lastLogon attribute is not replicated between DCs; to determine the last logon time you have to examine it on all DCs.

Attrinbutes of interest:

Locating Computers Based on Computer Account Attributes:

Const ADS_SCOPE_SUBTREE = 2
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.CommandText = _
    "SELECT Name, Location, operatingSystemVersion FROM " _
    & "'LDAP://DC=fabrikam,DC=com' WHERE objectClass='computer' " _
    & "and operatingSystemVersion = '5.0 (2195)'"
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Timeout") = 30
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.Properties("Cache Results") = False
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    Wscript.Echo "Computer Name: " & objRecordSet.Fields("Name").Value
    Wscript.Echo "Location: " & objRecordSet.Fields("Location").Value
    objRecordSet.MoveNext
Loop

Trust Relation Ships

When you want to document your configuration, it is useful to have a list of all domain trust relation ships, especially during migrations, when you usually have a mix of trusts between Windows domains. Windows NT account domains, and Windows NT 4.0 resource domains. Each trust relationship in a domain has a trusted domain object that resides in the System container in the Domain naming context:

Creating User Accounts

When creating a new user account, you must set the CN (unique in the account's OU) and Samaccountname (unique in the domain) attributes. The userPrincipalName attribute (unique in the enterprise) is optional. To create a new user in the OU:

Dim salesOU as IADsContainer
Set salesOU = GetObject("LDAP://OU=Sales,DC=Fabrikam,DC=COM")
Set usr = salesOU.Create("user", "CN=Jay Adams")
usr.Put "sAMAccountName", "jayadams"
usr.Put "userPrincipalName", "jayadams@fabrikam.com" 
usr.Put "title", "Marketing Manager"
usr.SetInfo

usr.SetPassword "seahorse"
usr.AccountDisabled = False
usr.SetInfo

Creating Groups

There are three scopes for groups:

Groups are of these types:

Group type is required. Specify an integer that contains the flags that specify the group type and scope using these combinations:

The process of creating a group is similar to other objects. First bind to the OU which will contain the group. Next create the group object. Then set the group type, and samAccountName for down-level clients.

Set ou = GetObject( "LDAP://OU=DSys,DC=Northwind,DC=tld" )
Set grp = ou.Create( "group", "CN=Distributed System Admin" )

' Creating a domain local group
grp.Put "groupType", ADS_GROUP_TYPE_LOCAL Or ADS_GROUP_TYPE_SECURITY_ENABLED
grp.Put "samAccountName", "DSysAdmin"
grp.SetInfo

Use the add method to add members to a group using the path to the user's object.

' Adding a user to a group
grp.Add( "LDAP://CN=James Smith,OU=Marketing,OU=DSys,DC=Northwind,DC=tld" )

Managing Enterprise Configuration Information

Configuration information is global information shared among all domains in the enterprise and usually managed by the enterprise administrator.

Partitions

The partitions container's crossRef objects list the enterprise naming contexts - one for Configuration, one for Schema and one for each Domain. You can add objects to point to partitions on other LDAP servers that are not part of the enterprise, in which case you generate an LDAP referral to the proper partition when requesting an object from that portion of the namespace.

This script enumerates crossRef objects in the partitions container:

On Error Resume Next
 
msgbox "This script enumerates crossRef objects in the partitions container."
 
sPrefix = "LDAP://"
 
' Get distinguished name for config container and build ADsPath to partitions container.
Set root= GetObject(sPrefix & "rootDSE")
If (Err.Number <> 0) Then
    BailOnFailure Err.Number, "on GetObject method for rootDSE"
End If
sConfigDN = root.Get("configurationNamingContext")
If (Err.Number <> 0) Then
    BailOnFailure Err.Number, "on Get method"
End If
sContainerDN = "cn=Partitions," & sConfigDN
 
'''''''''''''''''''''''''''''''''''''''
' Bind to the container
'''''''''''''''''''''''''''''''''''''''
Set cont= GetObject(sPrefix & sContainerDN)
If (Err.Number <> 0) Then
    BailOnFailure Err.Number, "on GetObject method for partitions container"
End If
'''''''''''''''''''''''''''''''''''''''
' Enumerate the container.
''''''''''''''''''''''''''''''''''''''
For Each obj In cont
    strText = strText & "Name: " & obj.Get("name") & vbCrLf
    values = obj.GetEx("objectClass")
    For Each value In values
       sValue = value
    Next
    strText = strText & "  objectClass: " & sValue & vbCrLf
    strText = strText & "  DnsRoot: " & obj.Get("dnsRoot") & vbCrLf
    strText = strText & "  NCName: " & obj.Get("NCName") & vbCrLf
    sTrustParent = obj.Get("trustParent")
    If (Err.Number = 0) Then
       strText = strText & "  TrustParent: " & sTrustParent & vbCrLf
    Else
       Err.Clear
    End If
    sNetBIOSName = obj.Get("nETBIOSName")
    If (Err.Number = 0) Then
       strText = strText & "  NETBIOSName: " & sNetBIOSName & vbCrLf
    Else
       Err.Clear
    End If
Next

show_items strText, "Display crossRef"


'''''''''''''''''''''''''''''''''''''''
' Display subroutines
'''''''''''''''''''''''''''''''''''''''
Sub show_items(strText, strName)
    MsgBox strText, vbInformation, "Create CrossRef"
End Sub
 
Sub BailOnFailure(ErrNum, ErrText)    strText = "Error 0x" & Hex(ErrNum) & " " & ErrText
    MsgBox strText, vbInformation, "ADSI Error"
    WScript.Quit
End Sub

Listing Domain Controllers

Each domain controller in the enterprise is represented by several objects:

To get a list of all enterprise DCs, issue a query with a base path of the site's container that searches for object category nTDSDSA. Some attributes of interest:

Listing domain controllers:

' Get the Configuration Naming Context
Set oRootDSE = GetObject("LDAP://RootDSE")
strConfigNC = oRootDSE.Get("configurationNamingContext")

' Set up the oConnectionection
set oConnection = CreateObject("ADODB.Connection")
oConnection.Provider = "ADsDSOObject"
oConnection.Open "ADs Provider"

' Build the query
strQuery = "<LDAP://" & strConfigNC & 
">;(objectClass=nTDSDSA);ADsPath;subtree"

set oCmd = CreateObject("ADODB.Command")
oCmd.ActiveConnection = oConnection
oCmd.CommandText = strQuery
Set oRecordset = oCmd.Execute

' Iterate through the results
If oRecordset.Eof and oRecordSet.Bof Then
  WScript.Echo "No Domain Controllers were found"
Else
  While Not oRecordset.EOF
    Set oParent = GetObject(GetObject(oRecordset.Fields("ADsPath")).Parent)
    ' Output the name of the server
    WScript.Echo "Server: " & oParent.cn & " dNSHostName: " & oParent.dNSHostName
    oRecordset.MoveNext
  Wend
End if

FSMO

Flexible Single-Master Operations (FSMO) implements operations (there are only a few) that must be handled in a single master replication model. There are five FSMO roles, two per enterprise and three per domain, and you can use scripting to find out which DCs hold which FSMO roles.

Schema Master

The Schema Master is unique in the entire enterprise; it alone can create new classes or attributes, after which it replicates updates to all domains in the forest. To identify it, read the value of fsmoRoleOwner attribute on the Schema container found under the Configuration container. Schema and Configuration naming contexts are enterprise wide and reside on all domain controllers.

Set objRootDse = GetObject( "LDAP://RootDse" )
Set objSchema = GetObject( "LDAP://" & objRootDse.get( "SchemaNamingContext" ))
WScript.Echo objSchema.fsmoRoleOwner

Domain Naming Master

The Domain Naming Master is unique in the enterprise; it manages the addition and removal of domains into the forest. To identify it, read the partitions container object and examine its fsmoRoleOwner attribute:

Set objRootDse = GetObject( "LDAP://RootDse" )
Set objDomains = GetObject( "LDAP://CN=Partitions," & _
    objRootDse.Get( "ConfigurationNamingContext" ))
WScript.Echo objDomains.fsmoRoleOwner

PDC Emulator

The PDC Emulator is a per-domain role. To identify it, read the value of the fsmoRoleOwner attribute on the DomainDNS object:

Set objRootDse = GetObject( "LDAP://RootDse" )
Set objPDC = GetObject( "LDAP://" & objRootDse.Get( "DefaultNamingContext" ))
WScript.Echo objDomains.fsmoRoleOwner

RID Master

The RID Master is a per-domain role. It allocates RID blocks for all DCs in a domain that are used for SIDs in security principals such as users and groups. To identify it, read the value of the fsmoRoleOwner attribute on the RIDManager object of name CN="Rid Manager$" found in the domain's System Container:

Set objRootDse = GetObject( "LDAP://RootDse" )
Set objRID = GetObject( "LDAP://CN=Rid Manager$,CN=System," & _
    objRootDse.Get( "DefaultNamingContext" ))
WScript.Echo objDomains.fsmoRoleOwner

Infrastructure Master

The Infrastructure Master is a per-domain role. To identify it, read the value of the fsmoRoleOwner attribute on the InfrastructureUpdate object for the domain:

Set objRootDse = GetObject( "LDAP://RootDse" )
Set objInfra = GetObject( "LDAP://CN=Infrastructure," & _
    objRootDse.Get( "DefaultNamingContext" ))
WScript.Echo objDomains.fsmoRoleOwner