Wrox Press
ASPToday
       962 Articles
  in the Solutions Library
  Log off
 
 
 
Professional .NET Programming  
 
ASPToday Subscriber's Article Byron Hynes
Using ADSI to create an Exchange 5.5 Mailbox for a new AD User
by Byron Hynes
Categories: Security/Admin
Article Rating: 3.5
Published on February 15, 2002
 
Content Related Links Discussion Comments Index Entries Downloads
 
Abstract
In this article, Byron Hynes will look at using the Active Directory Services Interface (ADSI) to automate the creation of an Exchange 5.5 mailbox for a new network user. In Part 1, he investigated the actions required to create that user account and make it ready for the first log-on. We’ve already seen how to create the user object in Active Directory (AD), create a home directory for the user, set the home directory property and logon script property in the user object, create a network share and set NTFS permissions, and assign the user to the correct security groups in AD. Now, he’ll go on to use ADSI and the Lightweight Directory Access Protocol (LDAP) to create an Exchange 5.5 Mailbox for the user, and enable it for use.
 
Article

In this article, I'm going to look at using the Active Directory Services Interface (ADSI) to automate the creation of an Exchange 5.5 mailbox for a new network user. In Part 1, I investigated the actions required to create that user account and make it ready for the first log-on. We've already seen how to create the user object in Active Directory (AD), create a home directory for the user, set the home directory property and logon script property in the user object, create a network share and set NTFS permissions, and assign the user to the correct security groups in AD. Now, I'll go on to use ADSI and the Lightweight Directory Access Protocol (LDAP) to create an Exchange 5.5 Mailbox for the user, and enable it for use.

Business Rationale

Because I work primarily in training and network infrastructure (and not in coding), I am often asked to explain the direct benefit to the client or user, especially if the client is being asked to spend money. In Part 1, we looked at why it is beneficial to have a script create our new users, but a couple of questions remain, as we look at the email component.

Why not use Exchange 2000?

Exchange 2000 does not have its own directory. This is a major change from Exchange 5.5 and earlier, which have a dedicated directory database, based on X.400 concepts. Instead, Active Directory (AD) is the directory for Exchange 2000. Exchange 2000 is one of the few major applications to leverage AD, but any AD-aware application can make use of information stored in AD.

If you are using Exchange 2000, you work with a user's mailbox settings by using ADSI to manipulate the User object in AD. As my colleague Doug Frisk describes it, "The Mailbox and the User are One." Exchange 2000 is a large-scale, powerful product; but that also makes it a complex product. Microsoft made a number of architectural changes to Exchange 2000, and there is a significant learning curve for Exchange administrators. There is also a hefty financial cost.

In my experience, even small organizations can see a great benefit in upgrading their domains to Windows 2000, but often do not see a comparable benefit come with the cost of upgrading their Exchange server from 5.5 to 2000. In short, they may not want to fix what isn't broken. I believe that Exchange 2000 was created with large organizations in mind, and many small organizations won't see cost-effectiveness in an upgrade.

Isn't there a connector to do this automatically?

Yes, there is. You can use the Exchange 2000 Active Directory Connector (ADC). However, the ADC is primarily designed to support a migration from Exchange 5.5 to Exchange 2000, or to support a mixed-mode Exchange installation: one in which some servers run Exchange 2000 while others run Exchange 5.5. Like Exchange 2000, the ADC can be complex to set up, but once set up, it can replicate changes automatically between your Exchange 5.5 Directory and your Active Directory.

In many circumstances, however, it is easier to simply create an Exchange 5.5 mailbox with the same script that creates the Active Directory user object. This article is based on that approach.

There is a trade-off. You must continue to manage User objects in AD and Exchange mailboxes separately, and you won't automatically see Exchange objects, such as custom recipients, in your AD directory. If these are requirements in your enterprise, you may wish to implement the ADC. In some cases, this may be the only route to go, but in our case, the scripted approach was more suitable.

Script implementation

The script discussed in this article is called from an Intranet web page. The page contains a form that specifies a user's first name, last name, user name, initial password, and which groups and distribution lists they should be members of. Part 1 presented the script steps to:

  • Create the User Object in the Active Directory
  • Create the User's Home Directory
  • Create a Network Share for the Home Directory
  • Set the NTFS permissions on the User's Home Directory
  • Set the User Account Properties, including Home Directory and Logon Script
  • Add the User to the correct Security Groups

This part takes the remaining steps:

  • Create an Exchange Mailbox for the user
  • Link that mailbox to the correct User
  • Set the mailbox permissions
  • Add the User's Mailbox to the correct Distribution Lists

The code presented here continues right after the code presented in Part 1, before the clean-up steps. This means that we are still bound to the new user's ADSI object. (In the article support material, the script snippet bind2user.asp demonstrates binding directly to the user, rather than creating the user.)

Create an Exchange Mailbox for the user

As with all ADSI tasks, we begin by binding to an object. Previously, we used LDAP to bind to Active Directory and bound to a container object called an " ou " or organizational unit. Now we are going to bind to the Recipients Container in our Exchange 5.5 directory. We're still going to use ADSI and LDAP. One of the many benefits to using ADSI is that the same approach works in many different places.

We want to bind to the container that will hold our user's mailbox. An Exchange 5.5 installation can have many recipient containers, or the default of just one called " Recipients ". You'll need to know the path, within Exchange to the correct folder. In my script, I put this value into a constant, since this script is designed for small organizations where it is likely that all users would be in the same container, and that container's name and path would be well known, or easy to find.

Remember that you bind to the object to manipulate its properties, but to the parent container to create a new one. Creating the user object is simple enough. It's done by calling the create method of the container. Notice that we are specifying what type of object to create: " organizationalPerson " is LDAP-speak for " mailbox ". For the creation to succeed, the canonical name ( cn ) must also be specified.

Set oMailboxContainer = GetObject (ADSMailboxPath)
if Err.number = 0 then
    Response.Write "Bound to the Exchange 5.5 Directory container: <I>" _
               & ADSMailboxPath & "</I><BR>"
else
    Response.Write "Error binding to the Exchange 5.5 Directory " _
&  container: <I>" & ADSMailboxPath & "</I><BR>"
    Err.Clear
end if
Set oMailbox = oMailboxContainer.Create _
("organizationalPerson", "cn=" + username)

There are a number of attributes that can now be set on the mailbox object.

During the technical review of this article, one of my reviewers asked which object properties are required, and which are not needed. Here's one of those "gotcha's". according to ADSI and Exchange 5.5 whitepaper, the only required attribute is " mailPreferenceOption ", which is described as "the mechanism by which this mailbox receives mail", and is always set to 0 !

The good news is that ADSI and LDAP give you a lot of flexibility and power. The other side of that coin is that it makes it easy to create a mailbox configuration that just doesn't work. Let's look at some of the other attributes we should set.

In order for the mail to function, we need to set the Home-MTA and Home-MDB values. These represent the user's home site & home server, and if you have a multi-server Exchange installation may need to be selected for each user. In my script, they are calculated based on the name of the server, organization and site, which is correct in any default single-server Exchange 5.5 site installation. If your exchange is not installed in a default configuration, change the script appropriately. You can actually write ADSI code to retrieve the organization name if you know the server's name, but that's beyond the scope of today's article.

In order to send and receive mail, the mailbox must have an address. Although the mailbox will function with only an SMTP address (the " mail " attribute), Exchange documentation states that an X400 address (logically and clearly called a " textEncodedORAddress " when it's an LDAP attribute) must also be specified. If you don't specify both, you are likely to get a mailbox that can exchange mail with other users on the same server, but not through any connectors or to the Internet. The Exchange Administrator tool automatically adds required addresses for you, but when using a script, you need to explicitly include them. To build the X400 address string, we used variables set at the top of the script. (See the complete script in the downloads.)

The MSDN library has more detailed information on these attributes in the white paper at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadsi/html/msdn_adsiexch.asp&WROXEMPTOKEN=1019106ZOd1qPkql20ApuY0pTR. The other attributes are used to populate the directory and provide more information. If you want to collect them, simply include the appropriate fields on your "new user" form.

oMailbox.cn = FullName
oMailbox.uid = UserName
oMailbox.Put "Home-MTA", ExchMTAPath
oMailbox.Put "Home-MDB", ExchMDBPath
oMailbox.mailPreferenceOption = 0
if GivenName <> "" then oMailbox.givenName = GivenName
if Surname <> "" then oMailbox.sn =Surname
if ExchSMTPMail <> "" then oMailbox.mail = ExchSMTPMail
X400_address = "c=" & X400_c & ";a=" & X400_a _ 
    & ";p=" & X400_p & ";o=" & X400_o & ";s=" & Surname & ";g=" & GivenName Response.Write "X400 address: " &
 X400_address & "<BR>" 
oMailbox.Put "textEncodedORAddress", X400_Address
if title <> "" then oMailbox.title = title
if department <> "" then oMailbox.department = department
if custattr1 <> "" then oMailbox.extension-attribute-1 = custattr1
if phonenumber <> "" then oMailbox.telephoneNumber = phonenumber
if officeloc  <> "" then oMailbox.physicaldeliveryofficename = officeloc
oMailbox.setinfo
if Err.number <> 0 then
       Response.Write "Error creating user mailbox.<BR>"
       Err.Clear
else
       Response.Write "Created mailbox: " & oMailbox.ADSPath & "<BR>"
end if

Link the new mailbox to the correct User

Exchange 5.5 mailboxes have an attribute called the associated WindowsNT account or Assoc-NT-Account that contains the Security Identifier (SID) of the user of the mailbox.

If you are accustomed to creating users with User Manager for Domains, the Exchange extensions automatically connect the user to the mailbox. If you are used to making new users in the Exchange Administrator, you have probably used the "Primary Windows NT Account" command button to browse and select a user, as shown in Figure 1.

i1

Figure 1: Using Exchange 5.5 Administrator to assign a primary Windows NT Account to a mailbox

The Exchange Administrator's graphical interface allows you select a user by name, but ADSI requires us to use the SID.

The SID is an attribute of the user in the domain. So, we can look up the SID for any user we can uniquely identify. The good news is that we're already bound to the User as an ADSI object (because we just created it). We can access the property called objectSid and not have to go looking for the user's security identifier (SID). The bad news is that SIDs are expressed in a variety of formats, and, of course, the AD SID isn't in the right format for Exchange.

We humans normally look at a SID as a string, like S-1-5-21-1935655697-308236825-1417001333 . This is something called a Security Descriptor Definition Language (SDDL) format. However, the SID is really a binary number, which is how AD works with it. There is an interesting knowledge base article (number Q131320) that describes how Windows converts a binary security identifier to this string representation. Unfortunately, the ADSI provider for Exchange doesn't want raw binary, or the SDDL, it wants a format called " HexString ", which, as you probably guessed, is basically a string representing the digits, in hexadecimal.

You can write code to convert the binary SID number to a string representation. You can find one subroutine at

http://cwashington.netreach.net/script_repository/view_scripts.asp?Index=371&ScriptType=vbscript&WROXEMPTOKEN=1019106ZOd1qPkql20ApuY0pTR.

However, Microsoft has made a COM object available for download to make it easier to work with SIDs and Security Descriptors through the ADSI interfaces. We will be working with the ADSSECURITY.DLL module in this article. The ADSSECURITY.DLL allows you to use a COM-based approach, and it exposes more functionality, including ACL functions that we need later, so we used it in our script.

AdsSecurity.DLL began as part of the ADSI Software Development Kit (SDK). Since its initial release, there have been a few improvements and bug-fixes that lead to revision. Unfortunately, not every version of the DLL file was properly marked as a new version. The particular code in the scripts for this article should work with any AdsSecurity.DLL dated May 17, 1999 or later.

In testing, I had some problems with an incorrect DLL version, and Microsoft Developer Support helped me out in describing the best way to ensure you have the correct version. This DLL is now part of the Platform Software Development Kit (SDK). The Platform SDK is very dynamic and changes frequently. It is now hosted on a new website:

http://www.microsoft.com/msdownload/platformsdk/sdkupdate?WROXEMPTOKEN=1019106ZOd1qPkql20ApuY0pTR

This new website contains all components to the Platform SDK and allows you to install individual parts like the Core, DirectX, Internet Development, IIS, MDAC, Exchange Server, and SQL Server SDKs. All of the ADSI documentation, libraries, include files, and samples are included in the Core SDK. The Core SDK can be installed by navigating to the link above and choosing the Core SDK option in the list on the left hand side. Once the Core SDK is installed all of the ADSI information can be found under the Microsoft SDK\Samples\NetDS\ADSI folder. ADsSecurity.DLL can be found in the ResourceKit folder under the ADSI folder mentioned above.

To access the interfaces in this component, you will have to make sure the component is registered wherever your scripts will run, in my case, that means on both the intranet web server and my development workstation. Copy the DLL file to an appropriate directory and type REGSVR32 ADSSECURITY.DLL from a command prompt in that directory.

The IADsSID interface exposes two methods: SetAS and GetAS . You simply call SetAS , passing the current format of the SID, and the SID (or it's representation), and then you call GetAS , specifying the format in which you would like the SID returned. In this case, we SetAS using the raw binary format from AD, and read it back as a hex string using GetAS . The beginning of the script has constants defined for the formats. (You can look at showsids.htm and showsids.asp in the article downloads to see some of the formats available.)

' **** Link the new mailbox to the correct user
Set oSid = CreateObject("ADsSID")
oSid.SetAs ADS_SID_RAW, oUser.objectSID
UserSidHex = oSid.GetAs(ADS_SID_HEXSTRING)
Response.Write "User's SID is: " & UserSidHex & "<BR>"
oMailbox.Put "Assoc-NT-Account", UserSIDHex
oMailbox.SetInfo
If Err.Number <> 0 then 
       Response.Write "Could not associate Mailbox with user.<BR>"
       Err.Clear
else
       Response.Write "Exchange Mailbox associated with user.<BR>"
end if

Once we have the HexString, we assign it to the Exchange Mailbox object's Assoc-NT-Account property and call setinfo .

This associates the mailbox with the user's account, but this is not sufficient to allow the user to actually use the mailbox. We need to assign the correct permissions to the mailbox.

Set the mailbox permissions

When you use the Exchange Administrator to set the associated Window's account, the Administrator program automatically sets the permissions, as well. (If you do not see the permissions tab, choose Tools , then Options , then on the permissions tab, click " Show Permissions page for all objects ".) See Figure 2.

i2

Figure 2: A user must have permissions on their own mailbox. The user role is normally assigned by the Administrator GUI

Unfortunately, when we set the Assoc-NT-Account through ADSI, the permissions are not set automatically. It may seem odd, but in this case being the "owner" (or associated NT account), does not automatically grant permissions on the mailbox - although the Administrator program might make it look like it does. At this stage, we have a user associated with the account, but without any rights, as in Figure 3. If you try to create this situation using just Exchange Administrator, the program won't normally let you, but the power of ADSI makes it easy to create this undesirable situation.

i3

Figure 3: A mailbox associated with a domain account using ADSI doesn't automatically receive permissions

The ADsSecurity.DLL component also lets us work with permissions on the mailbox. Before looking at the code to set permissions, you should understand three related terms: a security descriptor (SD) , an access control list (ACL) , and an access control entry (ACE) . A security descriptor is a construct that holds or represents the various security information about an object. Security descriptors are used in the NTFS file system, in Active Directory, and in Exchange 5.5. There may be differences in security descriptors, depending on the class of object they are protecting. Generally, a security descriptor contains four important pieces of information: the object's owner's SID, a primary Group (if applicable), a Discretionary Access Control List (DACL) and a System Access Control List (SACL).

SACLs and DACLs are both lists of Access Control Entries (ACEs). Each individual ACE specifies a particular user or group (by SID) and whether the ACE is allowing access or denying access. A SACL is used to control auditing of access of a resource, and is outside the scope of this article. We need to manipulate the DACL, with is the access control list that determines whether or not a user can access an object.

This is actually a very handy model. Once you know how to work with ADSI's IADsSecurityDescriptor, IADsAccessControlList, and IADsAccessControlEntry interfaces, you can work with security in scripts for several different objects: AD objects, files, shares, or mailboxes, for example, all with one class of objects.

To use their mailbox, a user must be able to send mail, receive mail, and modify the user attributes on their mailbox. These permissions are represented numerically, so the beginning of the script defines constants to represent them:

Const ADS_RIGHT_EXCH_MODIFY_USER_ATT = &H2
Const ADS_RIGHT_EXCH_MAIL_SEND_AS = &H8 
Const ADS_RIGHT_EXCH_MAIL_RECEIVE_AS = &H10

To work with the Security Descriptor, first we will create an object, called "oSecurity" from the IADsSecurity interface exposed in ADsSecurity.DLL:

' **** Adjust the permissions in the mailbox Security Descriptor

Set oSecurity = CreateObject ("ADsSecurity")

This object has a method, GetSecurityDescriptor, which will create a new object representing the existing Security Descriptor for the mailbox. We could get the SD for any other object that has an AdsPath the same way.

Because we only need to work with the Discretionary ACL, we'll create a new object from just the DACL of this SD.

In this case, we don't need to build a new Access Control List or new Security Descriptor, we do need to create a new Access Control Entry and add it to the existing DACL. To create our new ACE, we specify that it is an ACE that will allow access (as opposed to one that denies it), that the trustee will be our newly created user, and that the Access Mask (or list of allowed actions) will be the combination of sending mail, receiving mail and modifying user attributes.

Set oSDforMailbox = oSecurity.GetSecurityDescriptor (oMailbox.ADsPath)
Set oDACLforMailbox = oSDforMailbox.DiscretionaryACL
Set oAccessControlEntry = CreateObject ("AccessControlEntry")

oAccessControlEntry.AceType = ADS_ACETYPE_ACCESS_ALLOWED
TrusteeString = oSid.GetAs (ADS_SID_SAM)
oAccessControlEntry.Trustee = TrusteeString
oAccessControlEntry.AccessMask = ADS_RIGHT_EXCH_MAIL_SEND_AS _ 
              or ADS_RIGHT_EXCH_MAIL_RECEIVE_AS _
              or ADS_RIGHT_EXCH_MODIFY_USER_ATT
oDACLforMailbox.AddACE oAccessControlEntry
oSDforMailbox.DiscretionaryACL = oDACLforMailbox
oSecurity.SetSecurityDescriptor oSDforMailbox

Of course, we know exactly which user we want to make the trustee. For that matter, we're bound to an ADSI user object for the user, but the IADsAccessControlEntry still has to be told which security principal we want to make a trustee. According to the documentation I've found, we should be able to pass the LDAP path (or ADsPath) to represent the user, but I have not been able to get it to work unless I use the pre-Windows-2000 format of "Domain\User" for the username. (This is one of the many "gotcha's" I found in the process of pulling all of the user creation steps into one script.) To get the old format of the username, I used the GetAS method described earlier, but specified the ADS_SID_SAM format.

Finally, we replace the existing DACL with the one to which we've just added the ACE, and use SetSecurityDescriptor to assign the now modified SD back to the mailbox. After doing so, by checking the permissions in Exchange Administrator we see that our newly created user has the required permissions, and has been given the "user" role on the mailbox.

Note: Modifying the Security Descriptor using the objects available in ADsSecurity.DLL is another way of assigning the rights to the NTFS directory that was discussed in Part 1. The cacls command is a command-line utility that modifies the DACL. Once you understand the role of a Security Descriptor and the use of these objects, it's probably preferable to work with the SD this way, rather than shelling out to the command processor.

Add the User's Mailbox to the correct Distribution Lists

In Part 1, we added the user to Security Groups in the Active Directory, according to choices made on the form submitted. We use exactly the same process to add the user's Exchange 5.5 mailbox to Exchange distribution lists. (Again, because we are not using the ADC.)

If you review Part 1, you can see that all the field names for all group selection checkboxes begin with GROUPS_ followed by an integer counter. In the declarations and constants section of the script, we stored the name of each distribution list in an array. Since VBScript arrays are 0-indexed, our first form checkbox is called GROUPS_0. We loop through the possible Distribution Lists. If the array element is an empty string, it is not processed. In our implementation, we use the first check box (defaulted to ON) for an "All Staff" email distribution list, and it defaults to checked.

For the Security Groups in Active Directory, we had to bind to the Security Group and add a member. The same process is used for Distribution Lists. First, we bind to the Distribution List with GetObject, and then call the Add method of the Distribution List object.

For i = 0 to GroupCount - 1
GroupFormField = "Groups_" + CStr (i)
if request(GroupFormField) = "ON" then
    if distlistnames(i) <> "" then
        ' Bind to the Distribution List in the Exchange Directory
        Response.Write "Group: " & GroupFormField & _
         " (" & distlistnames(i) & ") <BR>"
        ADsThisDLPath = AdsDLPath & "/cn=" & distlistnames(i) 
        Set oDLObj = GetObject (ADsThisDLPath)
        if Err.Number <> 0 then  
            Response.Write "Could not bind to group: " _ 
& ADsThisDLPath & "<BR>"
            Err.Clear
        else
            Response.Write "Bound to group: " & oDLObj.ADsPath & "<BR>"
        end if
        oDLObj.Add oMailbox.ADSpath
              if Err.Number <> 0 then 
                ErrorMessage = "Could not add User to Group: Error #" _
                             & CStr(Err.Number) & _
                     "-" & Err.Description & "<BR>"
                Response.Write (ErrorMessage)
                     Err.Clear
        else
            Response.Write "User added to Group. <BR>"
        end if
    end if
end if
Next

Conclusion

And there you have it, one new network user, ready to go, email active. It takes about 2 seconds for this script to run, and you have to have pretty fast fingers to do it faster the old way.

Each of the steps in scripting the complete creation of a user and his or her mailbox is straightforward in itself, and most are documented. However, in my searching, I couldn't find a single place that pulled it all together, or warned of the pitfalls. Documentation of oddities like the differing formats of SIDs, the need to include "textEncodedORAddress" or the correct manipulation of an ACE were harder to come by and led to great frustration when we first tried to do this.

The script presented with this part of the article is pretty close to what we are using in production with clients. We did change from using CACLS to using the IADsSecurity components, and we process the AD Security Groups and Exchange DLs in one loop, not the two shown here. We also moved each major step into a subroutine.

Along with these changes, here are some future projects you can consider to expand on these ideas:

How should the script behave if a user already exists? Could it be used to reset things or to correct steps missed in a manual creation?

What would happen if you encapsulated this 400-line script into a compiled VB component?

How could you script the removal of a user, perhaps incorporating a mandatory 90 day "disabled" period before final removal?

The possibilities for scripting are endless, and can put fun back in your day as a sysadmin! (Honest.)

Special thanks to Brett Hatcher at Microsoft Developer Support.

Article Information
Author Byron Hynes
Technical Editors John Chapman, Vickie Pring
Project Manager Helen Cuthill
Reviewers Ezequiel Espindola, Chad Hutchinson

If you have any questions or comments about this article, please contact the technical editor.

 
 
Rate this Article
How useful was this article?
Not useful Very useful
Brief Reader Comments: Read Comments
Your name (optional):
 
 
Content Related Links Discussion Comments Index Entries Downloads
 
Back to top