Scripting Exchange Using VBScript and ADSI (Part 2)

The first part of my scripting series discussed ways of accessing and searching for Exchange objects such as users and contacts in Active Directory. This second part of the series will go over creation of a new object, and over the most important attribute of an Exchange recipient, the e-mail address.
Amit Zinman photo

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


Creation

You can use ADSI to access an object and also to create one but the Exchange operations involved, such as mailbox enabling a user, cannot be done with ADSI alone. Instead, CDOEXM, an interface created for Exchange management operations is used. When creating a new mailbox enabled user in Active Directory using scripting you can either start with ADSI and then use CDOEXM to complete the job or use just CDOEXM for both user and mailbox creation.

A user will need a name, a login name, a password. To mailbox enable the user you will need to know the database, storage group, server name, administrator group, organization name and domain name.

The following subroutine creates a mailbox-enabled user using ADSI and CDOEXM.

Sub ADSICreateMailBoxEnabledUser _
  (MDBName,StorageGroup,Server,AdminGroup,Organization,DomainName,emailname,FirstName,LastName)

'split the domain name to get the directory name for it
DomainDN     = "dc=" & Replace(DomainName,".",",dc=")

recip = "CN=" & emailname

' get the container
Set objContainer = GetObject("LDAP://CN=users," & DomainDN)

' create a recipient
Set objUser = objContainer.Create("User", recip)
objUser.Put "samAccountName", emailname
objUser.Put "sn", LastName
objUser.Put "givenName", FirstName
objUser.Put "userPrincipalName", emailname

objUser.SetInfo
objUser.SetPassword "123456" 
objUser.AccountDisabled = False

Set objMailbox = objUser

                objMailbox.CreateMailbox "LDAP://CN=" & MDBName & _
                               ",CN=" & StorageGroup & _
                               ",CN=InformationStore" & _
                               ",CN=" & Server & _
                               ",CN=Servers" & _
                               ",CN=" & AdminGroup & _
                               ",CN=Administrative Groups" & _
                               ",CN=" & Organization & _
                               ",CN=Microsoft Exchange,CN=Services" & _
                               ",CN=Configuration," & DomainDN

objUser.SetInfo

End Sub

At first, the domain name is parsed. The "." character is replaced by ",cn=" and another "cn=" is added to the beginning of the string. This will create the directory name "cn=domain,cn=local" from the domain name "domain.local".

Similarly, "CN=" is added to the object name designated by "emailname" to enable its creation later on in the script.

Now we get the container, in this case the "users" container. Please note that you can change this script so that the user will be created in a container or OU of your choosing. The "Create" command is run to create an object of type "User" in the container and then a list of properties is entered.

Now comes the strange looking line "Set objMailbox = objUser". This creates a new blank pointer object pointing to the ADSI user object just created. The first method implemented for this object, "CreateMailbox", will designate the pointer as accessing the "IMailboxStore" interface of a CDO.Person object.

Once the mailbox is created the ADSI user object "SetInfo" method is run and the user is created and mailbox enabled.

The following line I used when writing the final version of the above subroutine can be used to give you an idea of how to run it:

ADSICreateMailboxEnabledUser "Mailbox Store (EX2003)","First Storage Group", "EX2003", _
                           "First Administrative Group", "Sunnydale","sunnydale.muni", _
                           "testuser" ,"Test","User"

Creating a mailbox enabled user using just CDOEXM is not much different.

Sub CDOCreateMailBoxRecipient(MDBName, DomainName, _
                              exchangeOrg, adminGroup, _
                              storageGroup, storeName, _
                              ServerName, emailname, _
                              firstName, lastName)

'split the domain name to get the directory name for it

DomainDN  = "dc=" & Replace(DomainName,".",",dc=")

Set objPerson = CreateObject("CDO.Person")

 ' First, create the user in Active Directory.
 objPerson.FirstName = FirstName
 objPerson.LastName  = LastName
 objPerson.Fields("userPrincipalName") = LastName
 objPerson.Fields("userAccountControl") = 512
 objPerson.Fields("userPassword") = "password"
 objPerson.Fields.Update
 objPerson.DataSource.SaveTo "LDAP://CN=" & MDBName & _
                            ",CN=" & emailname & _
                            ",CN=users," & domainDN

 ' Now, create a mailbox in a specified location.

 Set objMailbox = objPerson.GetInterface("IMailboxStore")
 objMailbox.CreateMailbox "LDAP://CN=" & _
                         MDBName & _
                         ",CN=" & _
                         StoreName & _
                         ",CN=" & _
                         StorageGroup & ",CN=InformationStore,CN=" & _
                         ServerName & _
                         ",CN=Servers,CN=" & _
                         AdminGroup & "," & _
                         "CN=Administrative Groups,CN=" & _
                         ExchangeOrg & "," & _
                         "CN=Microsoft Exchange,CN=Services," & _
                         "CN=Configuration," & domainDN

 objPerson.DataSource.Save

End Sub

CDOEXM is a bit more of a sophisticated interface. It doesn't require you to use a pointer to the parent OU or Container for the user. But still, like in the ADSI script, you have one pointer to the Active Directory user properties (objPerson), and the other (objMailbox) for creating the mailbox itself.

You might notice that the CDOEXM script has a single property which was not in the ADSI one. The "userAccountControl" controls all kinds of account options. This script simply sets the user as "Normal Account" but there are a lot options that you can put. For more information about this property read the interesting Microsoft KB 305144.

Accessing E-mail Addresses

Exchange was created to be a flexible messaging server. Keeping that in mind the designers of Exchange decided that the e-mail addresses of an object will be an array of addresses. This array could contain any type of e-mail to fit any e-mail system even though Exchange originally was based on the X.400 standard. Connecting Exchange to a foreign e-mail system meant amongst others that each user could add another type of e-mail address to "speak" natively to that system. The Internet was originally such a foreign e-mail system and each user had an SMTP addresses to the default X.400 address, possibly more than one.

These days of course Internet e-mail addresses are the most important mail addresses a user has seeing that Exchange 2000/3 transfer messages using SMTP between servers and to the Internet. Some organizations might even send more e-mail to the Internet than internally.

In Active Directory without the Exchange schema extension, each user has a single e-mail address, stored using the attribute "mail". This attribute had no real use in the day to day workings of Active Directory but you could have scripted it for any purpose.

With Exchange 2000/3 this property points to the primary SMTP address of an object. A primary SMTP addresses is the one specified in the "Reply-To" field of e-mails. So an Exchange recipient can receive e-mails for multiple addresses but when sending e-mail to the Internet is represented only by the primary SMTP address.

The primary SMTP address itself is not stored in the "mail" attribute. Instead, it is stored in the proxyAddresses field as part of the e-mail array.

The primary e-mail address of each type is in uppercase, for example: "SMTP:testuser@sunnydale.muni". Additional addresses will be stored with the e-mail type in lowercase.

The following sample script reads the e-mails of an object in Active Directory:

Set MyUser = GetObject ("LDAP://CN=Administrator,CN=Users,DC=sunnydale,DC=muni")
For each email in MyUser.proxyAddresses
       WScript.Echo email
Next

It displays all of the e-mail addresses and their types. Here is a more advanced script displaying the primary SMTP addresses and then the secondary ones.

Set MyUser = GetObject ("LDAP://CN=Administrator,CN=Users,DC=sunnydale,DC=muni")
Secondary = ""
For each email in MyUser.proxyAddresses
       If Left (email,5) = "SMTP:" Then
WScript.Echo "Primary e-mail address: " & Mid (email,6)
       ElseIf Left (email,5) = "smtp:" Then
              Secondary = Secondary & "," & Mid (email,6)
       End If
Next
If Secondary  <> ""  Then WScript.Echo "Other e-mail addresses: " & Mid (Secondary,2)

It uses the Left function to determine if the e-mail is of type SMTP and the Mid function to extract the actual e-mail.

If you want to search for a particular e-mail in Active Directory you can do so by forming an LDAP query using the proxyAddresses attribute. This attribute is indexed so you can search inside the e-mail array of object when you execute the query.

Set rootDSE = GetObject("LDAP://RootDSE")
domainContainer =  rootDSE.Get("defaultNamingContext")
Set conn = CreateObject("ADODB.Connection")
conn.Provider = "ADSDSOObject"
conn.Open "ADs Provider"
Email = "smtp:tara@sunnydale.muni"
LDAPStr = "<LDAP://" & DomainContainer &  ">;(&(objectCategory=person)(proxyAddresses=" & email & "));adspath;subtree"
Set rs = conn.Execute(LDAPStr)
If rs.RecordCount = 1 Then
   Set oPerson = GetObject(rs.Fields(0).Value)
   WScript.Echo oPerson.displayName
End If

This type of search is not case sensitive so it does not matter whether you search for "smtp:tara@sunnydale.muni" or "SMTP:tara@sunnydale.muni".

Adding an E-mail Address

Manipulating the e-mail addresses list of an Exchange recipient requires some array manipulation functions.

The UBound function retrieves the number of objects stored in an array. In order to add an e-mail address to athe proxyAddresses array of a recipient you would need to expand the number of objects stored in the array.

The Redim Preserve function allows you to expand an array while preserving its contents.

    Set oUser = GetObject ("LDAP://CN=Buffy Summers,OU=Scoobies,DC=sunnydale,DC=muni")
    Set objRecip = oUser
    sAddress = "smtp:slayer@sunnydale.muni"
    bIsFound = False
    vProxyAddresses = objRecip.ProxyAddresses
    nProxyAddresses = UBound(vProxyAddresses)
    i = 0
    Do While i <= nProxyAddresses
          If vProxyAddresses(i) = sAddress  Then
             bIsFound = True
                Exit Do
          End If
          i = i + 1
    Loop
    If Not bIsFound Then
           ReDim Preserve vProxyAddresses(nProxyAddresses + 1)
           vProxyAddresses(nProxyAddresses + 1) = sAddress
           objRecip.ProxyAddresses = vProxyAddresses
           oUser.SetInfo
    End If

The script obtains a pointer to a user, the user's e-mail addresses array and the length of the array. It then checks whether the e-mail address already exists for the user. If it does not then it increases the length of the array so that there will be room for an additional e-mail address and finally adds the e-mail address itself.

You might also decide to add an e-mail and make it the primary e-mail address.

    Dim objRecip
    Dim mycontact' As ContactItem
    Dim proxies
    'On Error Resume Next
    Set oUser = GetObject ("LDAP://CN=Buffy Summers,OU=Scoobies,DC=sunnydale,DC=muni")
    Set objRecip = oUser
    sAddress = "SMTP:slayer@sunnydale.muni"
       bIsFound = False
       vProxyAddresses = objRecip.ProxyAddresses
       nProxyAddresses = UBound(vProxyAddresses)
       i = 0
       Do While i <= nProxyAddresses
          email = vProxyAddresses(i)
          If Left (email,5) = "SMTP:" Then 
                vProxyAddresses (i) = "smtp:" & Mid (email,6)
          End If       
          If vProxyAddresses(i) = sAddress  Then
             bIsFound = True
                Exit Do
          End If
          i = i + 1
    Loop
       If Not bIsFound Then
           ReDim Preserve vProxyAddresses(nProxyAddresses + 1)
           vProxyAddresses(nProxyAddresses + 1) = sAddress
           objRecip.ProxyAddresses = vProxyAddresses
              oUser.SetInfo
       End If

In this script I used the Left function to find which of the e-mail addresses is the primary SMTP address. Then the script manipulated it using the Mid function so it will begin with "smtp:" instead of "SMTP:", designating it as a secondary e-mail address.

Make sure that when you add an e-mail address it is properly formatted because Active Directory does not check that the format of the e-mail address is valid. You might get some really strange results if you add an improperly formatted e-mail address.

Removing an E-mail Address

Removing an e-mail address from a recipient is as important as adding one. Adding an e-mail address to, say a bunch of users, can be done using Exchange System Manager by changing or adding a recipient policy.

This will add to a each user another secondary e-mail address.

But what happens when we change Default Policy so that now there will be only one SMTP address ("sunnydale.org")?

As you can see the old e-mail address is not deleted when the recipient policy is changed so you get redundant e-mail addresses. These e-mail addresses can be deleted by using the following script:

Dim NewProxies ()
Set rootDSE = GetObject("LDAP://RootDSE")
DomainContainer =  rootDSE.Get("defaultNamingContext")
Set conn = CreateObject("ADODB.Connection")
conn.Provider = "ADSDSOObject"
conn.Open "ADs Provider"
emaildomain = "sunnydale.muni"
LDAPStr = "<LDAP://" & DomainContainer &  ">;(&(objectCategory=person)(proxyAddresses=*@" _
& emaildomain & "));adspath;subtree"
Set rs = conn.Execute(LDAPStr)
While Not rs.EOF 
          Set oRecipient = GetObject(rs.Fields(0).Value)
           bIsFound = False
              proxies = oRecipient.ProxyAddresses
              nProxies = UBound(proxies)
              i = 0
              n = 0
              Do While i < nProxies
                 checkproxy = proxies (i)
                 AddProxy = False
                 If (LCase(Left(proxies(i),5)) <> "smtp:") Then 
                            AddProxy = True 
                 Else
                    TempAr = Split (proxies(i),"@")
                   If (TempAr (1) <> emaildomain) Then AddProxy = True

                 End If
                 If AddProxy = True Then 
                    ReDim Preserve NewProxies (n)
                    NewProxies (n) = checkproxy
                    n = n + 1
                 End If
                 i = i + 1
              Loop

           oRecipient.PutEx 2,"ProxyAddresses",NewProxies
           oRecipient.SetInfo

          rs.movenext
Wend

The script obtains the current domain then performs an LDAP search for all the recipients which has an e-mail address that ends with the specified e-mail domain, in this case "sunnydale.muni". It then extracts current e-mail array into "proxies" and then goes through this array.

The Split function is used to split the email address into an array with two members. The second member is the e-mail domain. The script then adds all the e-mails that do not end with the e-mail domain into the new array "NewProxies".

Please note that "i" is the counter used for the original proxyAddresses array and "n" is used as a counter for the target array.

Now all the old e-mail addresses are gone. As you might notice it also deleted the custom e-mail address that the user had before (slayer@sunnydale.muni). If you don't want this to happen, a more complex script is in order.

Dim NewProxies ()
Set rootDSE = GetObject("LDAP://RootDSE")
DomainContainer =  rootDSE.Get("defaultNamingContext")
Set conn = CreateObject("ADODB.Connection")
conn.Provider = "ADSDSOObject"
conn.Open "ADs Provider"
emaildomain = "sunnydale.muni"
newemaildomain = "sunnydale.org"
LDAPStr = "<LDAP://" & DomainContainer &  ">;(&(objectCategory=person)(proxyAddresses=*@" _
& emaildomain & "));adspath;subtree"
Set rs = conn.Execute(LDAPStr)
While Not rs.EOF 
          Set oRecipient = GetObject(rs.Fields(0).Value)
           bIsFound = False
              proxies = oRecipient.ProxyAddresses
              nProxies = UBound(proxies)
              i = 0
              n = 0
              Do While i <= nProxies
                 checkproxy = proxies (i)
                 AddProxy = False
                 If (LCase(Left(proxies(i),5)) <> "smtp:") Then 
                            AddProxy = True 
                 Else
                      TempAr = Split (proxies(i),"@")
                   If (TempAr (1) = emaildomain) Then
                            checkproxy = TempAr(0) & "@" & newemaildomain
                            If Not (ExistsInProxies(checkproxy,proxies)) Then AddProxy = True
                      Else
                            checkproxy = True
                      End If              
                 End If
                 If AddProxy = True Then 
                    ReDim Preserve NewProxies (n)
                    NewProxies (n) = checkproxy
                    n = n + 1
                 End If
                 i = i + 1
              Loop

           oRecipient.PutEx 2,"ProxyAddresses",NewProxies
           oRecipient.SetInfo

          rs.movenext
Wend

Function ExistsInProxies (proxy,arProxies)
ExistsInProxies = False
m= UBound (arProxies)
j = 0
Do While j <= m
          If arProxies (j) = proxy  Then
             ExistInProxies = True
                Exit Do
          End If
          j = j + 1
Loop
End Function
    

The revised script adds a function that checks whether an e-mail address exists in the array. This is useful because now SMTP e-mail addresses are deleted if they have an e-mail suffix that you want to delete only if an alternative e-mail with the new suffix already exists. For example, buffy@sunnydale.muni is deleted because buffy@sunnydale.org exists. However, slayer@sunnydale.muni is not deleted. Instead it is changed to slayer@sunnydale.org.

Conclusion

We covered in this article, creation of a mailbox enabled recipient using ADSI and CDOEXM, and went in great detail into various scripts that can access e-mail addresses, add and remove them. The scripts grew in complexity as we progressed but were created with simplicity in mind. I would recommend playing with those scripts in the lab and perhaps writing what each command does on a piece of paper so that you can analyze the various loop and if structures. Getting on top of the logic of these scripts can help you understand Active Directory and Exchange better and also allow you to modify and customize the script to perform endless e-mail manipulations.

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

About Amit Zinman

Amit Zinman photo Currently working as Project Manager and Systems Consultant, heading and consulting on Exchange and NT/Windows 2000 based migrations and deployments for large companies such as Checkpoint, Comverse, Smarteam, Nice, Aladdin and leading Israeli Banks, Also involved in writing scripts and custom solutions for clients based on ADSI, CDO and Visual Basic and teaching Windows 2000 and Exchange 2000 in MSCE colleges and lecturing in Microsoft User Groups.

Click here for Amit Zinman's section.

Receive all the latest articles by email!

Get all articles delivered directly to your mailbox as and when they are released on MSExchange.org! Choose between receiving instant updates with the Real-Time Article Update, or a monthly summary with the Monthly Article Update. Sign up to the MSExchange.org Monthly Newsletter, written by Exchange MVP Henrik Walther, containing news, the hottest tips, Exchange links of the month and much more. Subscribe today and don't miss a thing!



Receive all the latest articles by email!

Receive Real-Time & Monthly MSExchange.org article updates in your mailbox. Enter your email below!
Click for Real-Time sample & Monthly sample

Become an MSExchange.org member!

Discuss your Exchange Server issues with thousands of other Exchange experts. Click here to join!

Solution Center

Readers' Choice

Which is your preferred Email Archiving solution?