A useful tool for email-enabled account management
Over the past year, I've been discussing how to automate Exchange Server creation and deletion tasks. In the articles "Script User Account and Mailbox Creation," September 2002, http://www.exchangeadmin.com, InstantDoc ID 25843, and "Deleting User Accounts and Exchange 2000 Mailboxes," June 2003, InstantDoc ID 38529, I explain how to perform creation and deletion tasks in an Exchange 2000 Server environment. In the article "Automatically Create User Accounts and Mailboxes in Exchange 5.5," August 2003, InstantDoc ID 39177, I explain how to automate the process of creating Exchange Server 5.5 email-enabled user accounts. This article completes this discussion by explaining how to automate the process of deleting Exchange 5.5 mailboxes and Windows NT or Active Directory (AD) user accounts.
The scripting solution that I provide is called the RUM55 utility. RUM is short for Remove User and Mailbox; 55 designates that this solution is designed to delete mailboxes in an Exchange 5.5 directory only. RUM55 requires Active Directory Service Interfaces (ADSI) and Windows Script Host (WSH) 5.6, along with VBScript and XML, to make automating user account and Exchange 5.5 mailbox deletion an obtainable goal. I do my best to follow the outline of the "Deleting User Accounts and Exchange 2000 Mailboxes" article. My hope is that if you read the previous articles on this subject, you'll find this article even easier to follow.
You need to meet fewer prerequisites for automating the deletion of user accounts and mailboxes in Exchange 5.5 than for creating those user accounts and mailboxes. Fewer prerequisites exist not because the deletion task is easier to implement programmatically than the creation task, but rather because I choose to use ADSI exclusively to perform the deletion task. To perform the creation task, I used a COM object (AcctMgmt) that wasn't installed and registered by default. Because I'm using ADSI exclusively for the deletion task, you won't need to install or register any COM objects to use the RUM55 utility. However, you still must meet these prerequisites:
- Your network must run Exchange 5.5 with accounts in an AD or NT domain.
- You must be logged on to the domain that contains the user account you want to delete.
- You must have the necessary permissions to delete user accounts and mailboxes.
- The client computer on which you intend to run RUM55 must have WSH 5.6 installed. You need WSH 5.6 because RUM55 uses an improved method of managing command-line arguments and an XML file format, both of which I describe in "Automatically Create User Accounts and Mailboxes in Exchange 5.5."
- The client computer must have ADSI installed. ADSI is preinstalled on computers running Windows 2000 or later. If you plan to run the script from NT or Windows 9x, you must install the Active Directory Client Extensions. For more information about these extensions, including where to obtain them, see the Microsoft article "Active Directory Client Extensions for Windows 95/98 and Windows NT 4.0" (http://www.microsoft.com/windows2000/server/evaluation/news/bulletins/adextension.asp).
Running the Script
RUM55 consists of two scripts: RUM55.wsf and RUM55Code.vbs. The scripts are in RUM55_Utility.zip, which you can download from the Code Library (http://www.exchangeadmin.com, InstantDoc ID 39508). RUM55.wsf is a Windows Script file whose contents I've structured as XML code. (I explain why I use the XML format in "Automatically Create User Accounts and Mailboxes in Exchange 5.5.") RUM55Code.vbs is a VBScript file that contains code on which RUM55.wsf relies.
When RUM55.wsf runs, it calls RUM55Code.vbs. You can run RUM55.wsf from WSH's graphical interface (wscript.exe) or command-line interface (cscript.exe). For command-line help with RUM55.wsf, type
and press Enter. When you run RUM55.wsf from wscript.exe, the script displays Help and status information in a message box, as Figure 1 shows. When you run RUM55.wsf from cscript.exe, the script displays the same information in a command window. If you want to know why two script hosts exist, which is the default script host, and how to change that default, see "Automatically Create User Accounts and Mailboxes in Exchange 5.5."
As Figure 1 shows, the RUM55 utility requires three parameters (i.e., /a, /u, and /ru) to delete an NT user account and four parameters (i.e., /a, /u, /c, and /ru) to delete an AD user account. Figure 1 describes the values you specify for each parameter. Note that for AD user account deletion, the /c parameter must be the distinguished name (DN) of the container that hosts the user account. You can specify either a default AD container (e.g., cn=Users) or an organizational unit (OU—e.g., ou=Management).
If you omit a mandatory parameter, RUM55.wsf displays the information that appears in Figure 1. The parameters and their values aren't case sensitive, and the parameter order isn't important. If any of the parameter values contain spaces, you must enclose the value in quotation marks. For example, if you want to delete just the AD user account named EthanW in an OU named Tech Writers, you'd type
rum55 /a:ad /ru- /c:ou="Tech Writers" /u:EthanW
on one command line and press Enter. To delete both the mailbox and the user account, you'd change /ru- to /ru+.
As you can see, using the RUM55 utility is easy. Now let's look at how RUM55.wsf and RUM55Code.vbs work together to delete Exchange 5.5 mailboxes and AD or NT user accounts.
A .wsf file provides additional features that aren't available in .vbs or .js files. One such feature is XML elements that you can use. For example, you can use the <runtime> element to document a script's usage information. For more information about the XML elements, see "Automatically Create User Accounts and Mailboxes in Exchange 5.5."
RUM55.wsf includes the <script> element. Within the <script> element, the language attribute specifies the scripting language to use (in this case, VBScript) and the src attribute specifies the name and location of the source-code file. Because the src attribute doesn't specify a folder location, RUM55.wsf assumes that RUMCode55.vbs is in the same folder as RUM55.wsf.
Following some descriptive text, RUM55Code.vbs starts by declaring the Option Explicit statement, some constants, and several global variables. If you've written scripts before, you know that declaring this information is typical. If you aren't familiar with these scripting constructs, see the previous articles in this series.
Next, the script uses the WshArguments object's Named property to retrieve the command-line parameters (i.e., /a, /u, /ru, and possibly /c) that you entered when you launched the RUM55 utility. Web Listing 1 (http://www.exchangeadmin.com, InstantDoc ID 39508) shows how RUM55Code.vbs determines whether you included the three mandatory parameters (i.e., /a, /u, and /ru). If the WshNamed object's Count method returns the value of 0, 1, or 2, the WshArguments object's ShowUsage method displays the syntax information contained in the <runtime> element of RUM55.wsf, then RUM55Code.vbs terminates. If the Count method returns any other value, RUM55Code.vbs uses the WshNamed object's Exists method to verify that the three mandatory parameters are present. If the three mandatory parameters aren't present, the WshArguments object's ShowUsage method displays the syntax information, then RUM55Code.vbs terminates.
Assuming that the /a, /u, and /ru parameters are present, RUM55Code.vbs sets the strDomain variable to the value that the GetDomain function returns. The GetDomain function returns the current domain's name. The script assumes that you want to delete a user account in the domain to which you're currently logged on. GetDomain first determines whether you're attempting to delete an AD or NT user account. For an AD account, the function uses the AD RootDSE object's defaultNamingContext attribute to find the current domain. For an NT account, the function uses the WScript.Network object's UserDomain property to find the current domain.
Now that RUM55Code.vbs knows the domain name, the first Select Case statement in Web Listing 1 receives the value of the /a parameter. This Select Case statement attempts to bind to the user account object you specified with the /u parameter. If you specified NT for the /a parameter, the first Case block runs. If you specified AD for the /a parameter, the second Case block runs. If you specified anything else, the third Case block (Case Else) displays an error message, calls the ShowUsage method, and terminates the script.
As callout A in Web Listing 1 shows, the Case block for an NT account begins by making sure you didn't include the /c parameter. You use this parameter to specify the AD container in which the user account resides. NT doesn't use AD containers to store user accounts, so as long as you didn't specify the /c parameter, the script continues. Using the GetObject function with the WinNT provider, the script binds to the NT user account and creates an object reference to it, which the script assigns to the objUser variable. If the binding attempt fails, an error message appears and the script terminates.
As callout B in Web Listing 1 shows, the Case block for an AD account begins by verifying that you included the /c parameter because the location of the container in which the user account resides is important when attempting to bind to an AD user account. Using the GetObject function with the LDAP provider, the script binds to the AD user account object and creates an object reference to it, which the script assigns to the objUser variable. If the binding operation fails, an error message appears and the script terminates.
The second Select Case statement in Web Listing 1 receives the value of the /ru parameter. If you specified /ru- (i.e., you want to delete only the user account), the first Case block runs. If you specified /ru+ (i.e., you want to delete the user account and the mailbox), the second Case block runs. If you don't specify /ru+ or /ru-, the third Case block (Case Else) displays an error message, calls the ShowUsage method, and terminates the script.
As callout C in Web Listing 1 shows, the Case block that deletes only the user account calls the DeleteUser subroutine. The DeleteUser subroutine, which Web Listing 2 shows, first binds to the parent container of the user object. The parent container is a property of ADSI's core IADs interface. As callout A in Web Listing 2 shows, the GetObject function binds to the parent container and sets the resulting object reference to the objContainer variable. Next, DeleteUser calls the IADsContainer interface's Delete method. This method's second parameter, the user account value, differs depending on whether you're deleting an AD or NT account. Therefore, DeleteUser first determines the type of account you want to delete. Then, the Delete method deletes the user account and displays a message confirming the deletion.
The Case block that deletes a user account and an Exchange 5.5 mailbox is more interesting and complex than the Case block that deletes only a user account. To programmatically delete an Exchange 5.5 mailbox, the script must first match the SID of the user account with the SID of the user account's corresponding mailbox. This step is necessary because the SID is the value that links the mailbox to the user account. As callout D in Web Listing 1 shows, the script first uses the IADs interface's Get method to read the user account object's objectSID attribute. Then, the script calls the DeleteUser subroutine, which deletes the user account, and the DeleteMailbox subroutine, which deletes the mailbox. Web Listing 3 shows the DeleteMailbox subroutine.
To make the DeleteMailbox subroutine work properly in your environment, you must customize the values of the strEx55SrvrName and strMailBoxPath variables, which callout A in Web Listing 3 highlights. Set the strEx55SrvrName variable to the NetBIOS name of your Exchange 5.5 server. Set the strMailBoxPath variable to the path that leads to the container in which you've stored the Exchange 5.5 mailboxes. If you have one Recipients container, you can use a path such as "cn=Recipients,ou=Contoso,o=NDG". This path tells the DeleteMailbox subroutine to search the Recipients container in the Contoso OU in the NDG organization. If you store mailboxes in more than one container on a particular server, you need to specify a higher path in the Exchange 5.5 hierarchy. For example, the path "ou=Contoso,o=NDG" tells the DeleteMailbox subroutine to search all containers in the Contoso OU in the NDG organization.
After you set the strEx55SrvrName and strMailBoxPath variables, the DeleteMailbox subroutine can go to work. The subroutine's first task is to find the mailbox's DN by matching the SID of the user account with the SID assigned to the mailbox. To accomplish this task, DeleteMailbox calls the FindMB function, which has the syntax
FindMB(strMailSrvr, strMB, UserSID)
where strMailSrvr is the name of your email server, strMB is the mailbox path, and UserSID is the SID to find. The strEx55SrvrName and strMailBoxPath variables supply the values for the strMailSrvr and strMB parameters, respectively. FindMB obtains the UserSID parameter from the user account object's objectSID attribute. FindMB uses ADO and the ADSI OLE DB (i.e., ADsDSOObject) provider to search the Exchange 5.5 directory for the SID assigned to the mailbox (which the Assoc-NT-Account attribute specifies) and the mailbox's DN (which the distinguishedName attribute specifies).
If the Assoc-NT-Account attribute isn't empty (which can occur when a mailbox doesn't have an associated user account), the FindMB function compares the user account's SID with the SID assigned to the mailbox. In other words, the function compares the values of the objectSID and Assoc-NT-Account attributes, respectively. Although the values of the objectSID and Assoc-NT-Account attributes are both octet strings, the octet strings are in different formats. Therefore, FindMB converts the two values to a format that lets VBScript compare the values to determine whether they're equivalent. FindMB uses the GetAssocSID function to convert the Assoc-NT-Account attribute's value to a string and the GetSID function to convert the objectSID attribute's value to a string. If a match occurs, FindMB returns the matching mailbox's DN to the DeleteMailbox subroutine.
The DeleteMailbox subroutine stores the mailbox's DN in the strMbDN variable. Assuming that strMbDN isn't empty, DeleteMailbox uses the GetObject function with the LDAP provider to bind to the mailbox. Then, using an operation similar to the one I described for the DeleteUser subroutine, the DeleteMailbox subroutine deletes the mailbox from Exchange 5.5.
RUM55.wsf Is Running
Programmatically deleting a user account in AD or NT is simple, and you can find a lot of documentation to explain the process. Programmatically deleting an associated Exchange 5.5 mailbox isn't so simple because of the way in which user accounts and Exchange 5.5 mailboxes are associated. Therefore, if you're running Exchange 5.5 and need a way to automate the deletion of Exchange 5.5 mailboxes and user accounts, the RUM55 utility is a good solution.