Scripting Exchange Using VBScript and ADSI (Part 1)

If you missed the other articles in this series please read:

Introduction

Scripting can be a powerful tool. There are a lot well written scripts for use with Exchange floating around the Internet that can be quite useful. Changing to fit your needs is not too difficult, but writing your own scripts and making scripts work better for your needs requires some knowledge of the inner workings of Active Directory, Exchange and of course the scripting language.

This article will present a few basic scripting methods. I will explain in great detail both the scripting logic provided by VBScript and that of the interfaces provided by Active Directory.

Accessing Active Directory Objects

Exchange 2000/3 store their objects in Active Directory. To access user, contact and group information you need to be familiar with the way objects are stored and the LDAP notation that is used for accessing these objects.

Take for example a simple script to retrieve a user’s home server.

Dim MyUser
Dim HomeServer

Set MyUser = GetObject (“LDAP://CN=Administrator,CN=Users,DC=sunnydale,DC=muni”)
HomeServer = myUser.Get(“msExchHomeServerName”)
WScript.Echo “HomeServer is ” & HomeServer

The script begins with declaring two variables. In VBScript, unlike VB6, variables have no type until they are first assigned a value. In fact the first two line are redundant since you don’t really have to declare variables with VBScript. However, it might make the script more readable if you do declare them and is considered to be good practice.

Then the script sets the MyUser variable as a pointer to an Active Directory object, in this case the, the Administrator account which is located under the Users container of the domain. Pointers use the keyword “Set” to distinguish between them and other variables. Note that the HomeServer variable in the next line is set to equal a property of the user. However, it is not a pointer, so the keyword “Set” does not appear. To sum this up, MyUser points to an Active Directory object where the information is actually stored while HomeServer is now a variable of type string that stores some information obtained from Active Directory.

The GetObject function used to retrieve a pointer to Active Directory objects uses the LDAP notation. It has three types of objects or leafs:

CN = Containers, users, contacts, groups and other objects that usually don’t have child objects.
OU = Organizational Units which contain objects such users, contacts, groups and other OUs
DC = Domain containers which are created by separating the full internal domain name (in this example, sunnydale.muni)

You can view the Active Directory LDAP structure by installing the Windows 2000/3 Support Tools and running ADSIEdit.

This version of the script will also work:

Set MyUser = GetObject (“LDAP://CN=Administrator,CN=Users,DC=sunnydale,DC=muni”)
HomeServer = myUser.msExchHomeServerName
WScript.Echo “HomeServer is ” & HomeServer

Note that the “Get” function is not required to get a property of an object but you might encounter scripts that use it just the same.

The script queries the first domain controller it finds. If you are looking for a property that only exists on a global catalog server (GC) you should make that all your domain controllers are also global catalog servers or that you specify a particular GC like this:

Set MyUser = GetObject (“LDAP://GCName /CN=Administrator,CN=Users,DC=sunnydale,DC=muni”)

You may also query only GCs by using the GC:// notation as follows:

Set MyUser = GetObject (“GC://GCName /CN=Administrator,CN=Users,DC=sunnydale,DC=muni”)

You can also set properties for an object.

Set MyUser = GetObject (“LDAP://CN=Administrator,CN=Users,DC=sunnydale,DC=muni”)
MyUser.msExchHideFromAddressLists = True
MyUser.SetInfo

When you change a property for an object, the change is cached, not implemented right away. The SetInfo subroutine attempts to write the changed attributes to the object. Of course, Active Directory has all sorts of checks to determine whether the attributes are valid, and if one isn’t VBScript will throw an error.

Working with Collections

VBScript presents you with the option to work with collection. For example, you can change properties of all the files in single folder. With Active Directory this means you can set properties, for example, for all the users in an OU. To do this you set a pointer to the OU and enumerate all the users in it by treating it as a collection.

Set CNUsers = GetObject (“LDAP://CN=Users,DC=sunnydale,DC=muni”)
CNUsers.Filter = Array(“user”)
For Each User in CNUsers


     HomeServer = User.msExchHomeServerName
     DisplayName = User.displayName
     WScript.Echo “HomeServer for ” & DisplayName & ” is ” & HomeServer
Next



You can also select different operations for different types of objects (called classes) in an OU or simply count.

To this you use “Select Case” which serves as an extended “If”.

Set CNUsers = GetObject (“LDAP://CN=Users,DC=sunnydale,DC=muni”)
Contacts = 0
Users = 0
For Each User in CNUsers
      Select Case User.class
            Case “user”
                 Users = Users + 1
            Case “contact”
                 Contacts = Contacts +1
End Select 
Next
WScript.Echo “Users: ” & Users
WScript.Echo “Contacts: ”  & Contacts











You might also want to do a recursive search, that is, search in all the OUs within an OU.

For this you need to create a subroutine which can call itself.

Set TopLevel = GetObject (“LDAP://OU=Domain Users,DC=sunnydale,DC=muni”)
Contacts = 0
Users = 0
CountUsersContacts (TopLevel)


Sub CountUsersContacts (ObjOU)
For Each FoundObject in ObjOU
      Select Case FoundObject.class
            Case “user”
                 Users = Users + 1
            Case “contact”
                 Contacts = Contacts +1
            Case “organizationUnit”,”container”
                 CountUsersContacts (FoundObject)    
End Select 
Next









WScript.Echo “Users: ” & Users
WScript.Echo “Contacts: ”  & Contacts

So, the main program calls the subroutine “CountUsersContacts” with the top level OU “Domain Users” and if it finds an OU within the top level OU it calls the subroutine “CountUsersContacts” with this OU as input. Eventually what happens is that all the OUs and their child OUs are scanned.

If you want to scan the entire domain you can replace the following line:

Set TopLevel = GetObject (“LDAP://DC=sunnydale,DC=muni”)

If you’re like me, writing scripts for customers, so the domain name is not really a constant, you can add a few lines at the beginning of the script to find the root of the current domain.

Set rootDSE = GetObject(“LDAP://RootDSE”)
domainContainer =  rootDSE.Get(“defaultNamingContext”)
Set TopLevel = GetObject(“LDAP://” & domainContainer)

In LDAP 3.0, rootDSE is defined as the root of the directory data tree on a directory server. The rootDSE is not part of any namespace. The purpose of the rootDSE is to provide data about the directory server, in this case Active Directory. You can still tie this to a certain server if you want by changing this line:

Set rootDSE = GetObject(“LDAP://MyGlobalCatalogServer/RootDSE”)

Performing LDAP searches

Using recursive logic to search through a database is not the preferable way. It is slow because it reads every object it goes through and it is tough to debug because of the complex logic it uses. It is also memory consuming since you have multiple instances of the same function running at the same time.

Active Directory provides another way of searching by constructing an LDAP query. Constructing LDAP queries can be a complex matter.

The following table shows a few examples of LDAP filters used in searches:

Filter

Description

(objectCategory=*)

All objects

(&(objectClass=user)(!(cn=susan)))

All user objects except susan

(sn=sm*)

All objects with a surname that starts with sm

(&(objectClass=contact)(|(sn=Smith)
(sn=Johnson)))

All contacts with a surname equal to Smith or Johnson

As you can see, filters are constructed from operators that construct the logic, wildcards, allowing you a more relaxed search and regular LDAP objects and attributes. as shown in previous example

Commonly Used LDAP Search Filter Operators

Operator

Description

            =

                   Equal to

            ~=

                   Approximately equal to

            <=

                   Lexicographically less than or equal to

            >=

                   Lexicographically greater than or equal to

            &

                   AND

            |

                   OR

            !

                   NOT

Constructing or debugging an LDAP query, especially a complex one, is not an easy task as these can become very long and you might lose yourself in their syntax. Luckily for us scripting guys and girls Microsoft provides a way to construct and LDAP query using Exchange System Manager (ESM)

Run Exchange System Manager and create an Address List.

Right click All Address Lists and choose New…  Address List.

Give the address list a name and press the Filter Rules button.

This easy to use dialog box allows you to easily construct a query. In this example I’m looking for all of the Exchange Recipients that are located in Ohio and belong to the Finance department.

After entering the query that you desire press the OK button and then Finish.
Now you may ask, “Where is LDAP query”? On the property page of the newly created address list you can view it.

You can also press the Preview button to see the results of your Query. The LDAP query above can be copied and used in a script.

There is also, with Windows 2003 a way to create a query using Active Directory Users and Computers. The following screenshots will show how to construct a query that we shall use in our counting users and contacts script.

Now that the query string is constructed we are finally ready for a new version of the script.

Set rootDSE = GetObject(“LDAP://RootDSE”)
DomainContainer = rootDSE.Get(“defaultNamingContext”)

Set conn = CreateObject(“ADODB.Connection”)
conn.Provider = “ADSDSOObject”
conn.Open “ADs Provider”

ldapStr = “<LDAP://” & DomainContainer & “>;(&(&(& (mailnickname=*) (| (&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*)))(&(objectCategory=person)(objectClass=contact)) ))));adspath;subtree”

Set rs = conn.Execute(ldapStr)

While Not rs.EOF

      Set FoundObject = GetObject (rs.Fields(0).Value)
      Select Case FoundObject.class
            Case “user”
                 Users = Users + 1
            Case “contact”
                 Contacts = Contacts +1
      End Select  
      rs.MoveNext
Wend







WScript.Echo “Users: ” & Users
WScript.Echo “Contacts: ” & Contacts

A few lines have been added to open an LDAP connection to Active Directory by using ADSI, and “rs” is a pointer to a collection of directory names of objects returned as a result of the LDAP query after it is run.

Since it is not a collection of pointers to objects in Active Directory but only to their directory names, instead of using “For Each…In” we use a “While Not [object].EOF…Wend” loop that is advanced by using the “MoveNext” subroutine that advances the internal index of “rs”. To examine the Active Directory object itself, we call the GetObject function with rs.Fields(0).Value as input since it contains the directory name of the object.

This script still goes through every object. A more efficient script can simply return the number of users and the number of contacts by executing separate LDAP queries and using the “RecordCount” property of the collection as follows:

Set rootDSE=GetObject(“LDAP://RootDSE”)
DomainContainer = rootDSE.Get(“defaultNamingContext”)

Set conn = CreateObject(“ADODB.Connection”)
conn.Provider = “ADSDSOObject”
conn.Open “ADs Provider”
ldapStrContacts = “<LDAP://” & DomainContainer & “>;(&(&(& (mailnickname=*) (| (&(objectCategory=person)(objectClass=contact)) ))));adspath;subtree”
ldapStrUsers = “<LDAP://” & DomainContainer & “>;(&(&(& (mailnickname=*) (| (&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*))) ))));adspath;subtree”



Set rs1 = conn.Execute(ldapStrContacts)
WScript.Echo “Contacts : ” & rs1.RecordCount
Set rs2 = conn.Execute(ldapStrUsers)
WScript.Echo “Users : ” & rs2.RecordCount


Conclusion

We’ve gone through a few ways of accessing Active Directory objects and performing searches. This is of course just the tip of the iceberg when it comes to managing Active Directory and Exchange using scripts, but it’s definitely a good starting point since finding objects, reading and writing their properties is the foundation for a lot of Exchange related scripts.

If you missed the other articles in this series please read:


About The Author

Leave a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top