Utility Libraries for Exchange Scripting
Originally published July 11, 2006
I write many many many Exchange scripts. There are some things that are common in Exchange script - you need to get mailbox sizes, or a list of all the servers in an administrative group, or a list of all Exchange servers, etc. So, I ended up encapsulating all of those routines into a set of include files that I can easily use in my vbscripts. Most of these can easily be adapted for ASP usage with IIS (I've done so and made that easy - trust me).
My last post Finding disk space used by Exchange requires a number of these libraries.
Typically, I have a folder where I do all my scripting work (I typically call it something really original such as “C:\Scripts”). Below that I have an additional folder that I name “lib” for “library files” (you can tell that I originally worked in Unix and mainframes) where I place all of my include files. I have a short commentary about each file:
FILESTART: lib\constants.vbs
' This file contains various constants and pseudo-constants used by many
' of the other routines and other scripts I write. The ADS_* and wbem*
' constants are Windows constants. All of the others are used throughout
' various programs.
' Constants we need for ADSI calls
Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
Const ADS_PROPERTY_APPEND = 3
Const ADS_PROPERTY_DELETE = 4
Const ADS_ACETYPE_ACCESS_ALLOWED = 0
Const ADS_ACEFLAG_INHERIT_ACE = &H2
' Constants we need for WBEM calls
Const wbemFlagReturnImmediately = &H10
Const wbemFlagForwardOnly = &H20
' Constants we need for configuration
Const OU_HOSTING = "OU=Hosting" ' the OU where I put customer OU's
Const ExchServer = "ORANGE" ' the exchange server whose default mailbox store I'll use
Const Trustee = "Domain Admins" ' an extra ACL to add to the mailbox
' Are we a web application or not?
Const bWebApplication = False
Dim bDebug ' verbose output (used to be a constant)
bDebug = False
FILESTART: lib\ado.vbs
' This file contains routines that make using ADSI easier
Dim objCom ' the global ADO command object
Dim objConn ' the global ADO connection object
Dim Rs ' the global ADO resultset object
'
' InitializeADSI
' Purpose:
' Initialize the global ADO objects used for making
' ADSI queries.
'
' Inputs:
' None
'
' Outputs:
' None
'
' Requires:
' N/A
'
Sub InitializeADSI
Set objCom = CreateObject ("ADODB.Command")
Set objConn = CreateObject ("ADODB.Connection")
' Open the connection.
objConn.Provider = "ADsDSOObject"
objConn.Open "ADs Provider"
End Sub
'
' DoneWithADSI
' Purpose:
' Clean up the global ADO objects.
'
' Inputs:
' None
'
' Outputs:
' None
'
' Requires:
' Must be called after InitializeADSI
'
Sub DoneWithADSI
objConn.Close
Set objCom = Nothing
Set objConn = Nothing
End Sub
'
' DoLDAPQuery
' Purpose:
' Execute a paged LDAP query, using the
' global ADO objects.
'
' Inputs:
' strLDAPQuery - the LDAP query to execute
'
' Outputs:
' resultSet - the result set from the query
'
' Requires:
' Must be called after InitializeADSI
'
Sub DoLDAPQuery (strLDAPQuery, resultSet)
dp "LDAP query: " & strLDAPQuery
objCom.ActiveConnection = objConn
objCom.Properties ("Page Size") = 1000
objCom.CommandText = strLDAPQuery
Set resultSet = objCom.Execute
End Sub
'
' FinishLDAPQuery
' Purpose:
' Clean up the result set after an LDAP query.
'
' Inputs:
' resultset - a result set from DoLDAPQuery
'
' Outputs:
' None
'
' Requires:
' Must be called after DoLDAPQuery
'
Sub FinishLDAPQuery (resultSet)
resultSet.Close
Set resultSet = Nothing
End Sub
FILESTART: lib\report.vbs
' This file contains routines that make it easy to output
' debugging information and to switch back and forth
' between console and web page output. I get so tired
' of typing “wscript.echo blah blah blah“ so many times...
'
' CleanItUp
' Purpose:
' When using these reporting routines in an ASP or HTA
' application, this routine properly translates some
' special characters for output/display purposes.
'
' Inputs:
' str - the string to be cleaned up
'
' Outputs:
' Function value - the cleaned up string
'
' Requires:
' bWebApplication from constants.vbs
'
Function CleanItUp (str)
If bWebApplication Then
Dim str1
str1 = Replace (str, "<", "<")
str1 = Replace (str1, ">", ">")
CleanItUp = str1
Else
CleanItUp = str
End If
End Function
'
' e
' Purpose:
' An "echo" routine to output information to the end-user
'
' Inputs:
' str - The string to be output
'
' Outputs:
' None
'
' Requires:
' bWebApplication from constants.vbs
'
Sub e (str)
If bWebApplication Then
response.write CleanItUp (str) & "<br>"
Else
wscript.echo str
End If
End Sub
'
' p
' Purpose:
' An "echo" routine that starts a new paragraph to output
' information to the end-user
'
' Inputs:
' str - The string to be output
'
' Outputs:
' None
'
' Requires:
' bWebApplication from constants.vbs
'
Sub p (str)
If bWebApplication Then
response.write "<p>" & CleanItUp (str) & "<br>"
Else
wscript.echo vbCrLf & str
End If
End SUb
'
' dp
' Purpose:
' A "debug.print" routine to output information to the end-user
'
' Inputs:
' str - The string to be output
'
' Outputs:
' None
'
' Requires:
' bDebug from constants.vbs
'
Sub dp (str)
If bDebug Then e CleanItUp (str)
End Sub
Sub ErrorReport
e "***** Error: " & Err.Description & " (0x" & Hex (Err.Number) & ")"
End Sub
Sub WarnReport
e "***** Warning: " & Err.Description & " (0x" & Hex (Err.Number) & ")"
End Sub
FILESTART: lib\systeminfo.vbs
' This file contains basic routines that acquire information about the
' the Exchange organization (its name and its default SMTP address)
' as well as the computer that the script is being run upon (its Windows
' domain and the computer name)
Dim strNetBIOSDomain ' the NetBIOS domain this program is running in
Dim strNetBIOSComputer ' the NetBIOS name of the computer this program is running on
Dim strConfigNC ' the configuration naming context of this domain
Dim strDomainNC ' the domain naming context of this domain
Dim strOrgDN ' the distinguished name of the Exchange organization
Dim strDefaultDomain ' the default SMTP domain for the Exchange organization
'
' GetSystemInfo
' Purpose:
' Determine the necessary system variables to be
' able to make queries from Active Directory. Also,
' initialize ADO/ADSI global variables, and get
' Exchange organization information.
'
' Inputs:
' None
'
' Outputs:
' Causes many global variables to be initialized
'
' Requires:
' None
'
Function GetSystemInfo
Dim objSystemInfo, objWSHNetwork
Call InitializeAD
' get the NetBIOS domain name
Set objSystemInfo = CreateObject ("ADSystemInfo")
strNetBIOSDomain = objSystemInfo.DomainShortName
dp "strNetBIOSDomain: " & strNetBIOSDomain
Set objSystemInfo = Nothing
' get the NetBIOS computer name
Set objWSHNetwork = CreateObject ("WScript.Network")
strNetBIOSComputer = objWSHNetwork.ComputerName
dp "strNetBIOSComputer: " & strNetBIOSComputer
Set objWSHNetwork = Nothing
Call InitializeADSI
If GetOrganizationInformation Then
Call ClearSystemInfo
GetSystemInfo = True
Exit Function
End If
Call GetDefaultSMTPDomain
GetSystemInfo = False
End Function
'
' ClearSystemInfo
' Purpose:
' Does the necessary cleanup to exit the program.
'
' Inputs:
' None
'
' Outputs:
' None
'
' Requires:
' None
'
Sub ClearSystemInfo
Call DoneWithADSI
End Sub
'
' InitializeAD
' Purpose:
' Obtain the naming context strings for the current Active
' Directory domain.
'
' Inputs:
' None
'
' Outputs:
' None
'
' Requires:
' None
'
Sub InitializeAD
Dim objRootDSE
' Get the forest root directory services entry object
Set objRootDSE = GetObject ("LDAP://RootDSE")
' Exchange information is stored in the Configuration tree of the forest root
strConfigNC = objRootDSE.Get ("configurationNamingContext")
' this gives us the DN to the forest root
strDomainNC = objRootDSE.Get ("defaultNamingContext")
dp "Configuration Naming Context: " & strConfigNC
dp "Domain Naming Context: " & strDomainNC
Set objRootDSE = Nothing
End Sub
'
' GetOrganizationInformation
' Purpose:
' Set the global variable strOrgDN which contains the
' distinguished name of the Exchange organization, which
' is based in the configuration container.
'
' Inputs:
' None
'
' Outputs:
' Sets the global strOrgDN variable
'
' Requires:
' strConfigNC from InitializeAD
' All the ADO routines
'
Function GetOrganizationInformation
Dim strQuery
GetOrganizationInformation = False
' Build a query to find the Exchange organization.
strQuery = "<LDAP://CN=Microsoft Exchange,CN=Services," & strConfigNC & ">;" & _
"(objectCategory=msExchOrganizationContainer);" & _
"name,distinguishedName;" & _
"onelevel"
strOrgDN = ""
Call DoLDAPQuery (strQuery, Rs)
' If there are any results, there will only be one result. There
' may only be one Exchange organization per Active Directory forest.
e "Exchange Organization Name: " & Rs.Fields ("name")
dp "Organization DN: " & Rs.Fields ("distinguishedName")
strOrgDN = Rs.Fields ("distinguishedName")
Call FinishLDAPQuery (rs)
If Len (strOrgDN) = 0 Then
e "Cannot find Exchange organization information"
GetOrganizationInformation = True
End If
End Function
'
' GetDefaultSMTPDomain
' Purpose:
' Obtain the default SMTP domain for the Exchange organization.
' This is normally the same as the forest root domain (until
' changed by an Exchange administrator).
'
' Inputs:
' None
'
' Outputs:
' Set the global strDefaultDomain
'
' Requires:
' strOrgDN from GetOrganizationInformation
'
Sub GetDefaultSMTPDomain
Dim strAddress ' string
Dim strAddresses ' collection of addresses
Dim objPolicy ' reference to Default Recipient Policy
strDefaultDomain = ""
Set objPolicy = GetObject ("LDAP://CN=Default Policy,CN=Recipient Policies," & strOrgDN)
strAddresses = objPolicy.Get ("gatewayProxy")
' strAddresses contains all the addresses specified on the
' E-Mail Addresses tab of the default recipient policy
For Each strAddress in strAddresses
If Left (strAddress, 5) = "SMTP:" Then
strDefaultDomain = Right (strAddress, Len (strAddress) - 6) ' strip SMTP:@
End If
dp "Email address suffix: " & strAddress
Next
' Report our results
e "Default SMTP address for organization: " & strDefaultDomain
Set strAddresses = Nothing
Set objPolicy = Nothing
End Sub
FILESTART: lib\queries.vbs
' This file contains the routines to find out about servers, storage groups, and
' stores in an Exchange organization. Each individual routine is preceeded
' by individual documentation.
' all lists are semi-colon separated
' a list of all the servers in the organization
' and a parallel list containing their distinguished names
Dim strServerList, strServerListDN
' a list of all OAL's in the organization
' and a parallel list containing their adminText attribute
Dim strOALList, strAdminList
' a list of all administrative groups in the org
' and a parallel list containing their distinguished names
Dim strAGroupList, strAGroupListDN
' a list of all servers in an admin group
Dim strServerAGList
' a list of all storage groups on a server
' and a parallel list containing their distinguished names
Dim strServerSG, strServerSGDN
' a list of all the stores in a storage group (by distinguished name)
' and a parallel list of their types (public or private)
Dim strStoreDN, strStoreType
'
' GetAllServers
' Purpose:
' Get a list of all servers in the Exchange organization, and
' prepare two semi-colon separated lists - one of the short
' server name and one of the server distinguished name.
'
' Inputs:
' None
'
' Outputs:
' Sets the value of the global variables strServerList and
' strServerListDN
'
' Requires:
' strOrgDN from GetOrganizationInformation
'
Sub GetAllServers
Dim strQuery
' Get the list of all servers within the organization
strQuery = "<LDAP://" & "CN=Administrative Groups," & strOrgDN & ">;" & _
"(objectCategory=msExchExchangeServer);" & _
"name,cn,distinguishedName;" & _
"subtree"
strServerList = ""
strServerListDN = ""
Call DoLDAPQuery (strQuery, Rs)
p "All Exchange Servers in forest " & strDomainNC
While Not Rs.EOF
' output the current server found
dp "Server CN: " & Rs.Fields ("cn")
e vbTab & "Server Name: " & Rs.Fields ("name")
dp "Server DN: " & Rs.Fields ("distinguishedName")
strServerList = strServerList & ";" & Rs.Fields ("name")
strServerListDN = strServerListDN & ";" & Rs.Fields ("distinguishedName")
Rs.MoveNext
Wend
strServerList = Mid (strServerList, 2)
strServerListDN = Mid (strServerListDN, 2)
Call FinishLDAPQuery (Rs)
' Report our results
e " "
dp "strServerList = " & strServerList
dp "strServerListDN = " & strServerListDN
dp " "
End Sub
'
' GetAllOfflineAddressLists
' Purpose:
' Prepare two semi-colon separated lists, one containing the
' offline address lists present in the Exchange organization,
' and the other containing the adminDescription field for the
' OAL (used to store business logic information).
'
' Inputs:
' None
'
' Outputs:
' Set the value of the global variables strOALList and
' strAdminList.
'
' Requires:
' strOrgDN from GetOrganizationInformation
'
Sub GetAllOfflineAddressLists
Dim strQuery
Dim str
' Get the list of all offline address lists within the organization
strQuery = "<LDAP://" & "CN=Offline Address Lists,CN=Address Lists Container," & strOrgDN & ">;" & _
"(objectCategory=msExchOAB);" & _
"name,adminDescription;" & _
"onelevel"
strOALList = ""
strAdminList = ""
Call DoLDAPQuery (strQuery, Rs)
dp "All OALs in DN " & "CN=Offline Address Lists,CN=Address Lists Container," & strOrgDN
While Not Rs.EOF
str = Rs.Fields ("adminDescription")
If IsNull (str) or (Len (str) = 0) Then
str = "<null>"
End If
strOALList = strOALList & ";" & Rs.Fields ("name")
strAdminList = strAdminList & ";" & str
Rs.MoveNext
Wend
strOALList = Mid (strOALList, 2)
strAdminList = Mid (strAdminList, 2)
Call FinishLDAPQuery (Rs)
' Report our results
dp "strOALList = " & strOALList
dp "strAdminList = " & strAdminList
End Sub
'
' GetAdministrativeGroupInformation
' Purpose:
' Prepare a semi-colon separated list containing all administrative
' groups in the Exchange organization.
'
' Inputs:
' None
'
' Outputs:
' Set the value of the global variable strAGroupList
'
' Requires:
' strOrgDN from GetOrganizationInformation
'
Sub GetAdministrativeGroupInformation
Dim strQuery
' Get the list of all Administrative Groups within the organization
strQuery = "<LDAP://CN=Administrative Groups," & strOrgDN & ">;" & _
"(objectCategory=msExchAdminGroup);" & _
"name,cn,distinguishedName;" & _
"onelevel"
strAGroupList = ""
strAGroupListDN = ""
Call DoLDAPQuery (strQuery, Rs)
While Not Rs.EOF
' output the current administrative group found
dp "Admin Group CN: " & Rs.Fields ("cn")
dp "Administrative Group Name: " & Rs.Fields ("name")
dp "Admin Group DN: " & Rs.Fields ("distinguishedName")
strAGroupList = strAGroupList & ";" & Rs.Fields ("name")
strAGroupListDN = strAGroupListDN & ";" & Rs.Fields ("distinguishedName")
Rs.MoveNext
Wend
strAGroupList = Mid (strAGroupList, 2)
strAGroupListDN = Mid (strAGroupListDN, 2)
Call FinishLDAPQuery (Rs)
' Report our results
dp "strAGroupList = " & strAGroupList
End Sub
'
' GetServersForAdministrativeGroup
' Purpose:
' Prepare a semi-colon separated list of all servers in the
' Exchange organization. This shows an alternate method of
' obtaining the GetAllServers list, doing a "onelevel" search
' instead of a "subtree" search. However, since the query uses
' an indexed attribute, objectCategory, the added complexity
' does not buy any performance benefit.
'
' Inputs:
' None
'
' Outputs:
' Set the value of the global variable strServerAGList
'
' Requires:
' strOrgDN from GetOrganizationInformation
'
Sub GetServersForAdministrativeGroup
' Get the list of servers for each administrative group
' (this is provided just in case you are interested)
Dim strQuery, strServerAGList
Dim arr
Dim i
arr = Split (strAGroupList, ";")
strServerAGList = ""
For i = LBound (arr) To UBound (arr)
e "Exchange Servers for Administrative Group: " & arr (i)
strQuery = "<LDAP://" & _
"CN=Servers,CN=" & arr (i) & ",CN=Administrative Groups," & strOrgDN & ">;" & _
"(objectCategory=msExchExchangeServer);" & _
"name,cn,distinguishedName;" & _
"onelevel"
Call DoLDAPQuery (strQuery, Rs)
While Not Rs.EOF
' output the current server found
dp "Server CN: " & Rs.Fields ("cn")
e "Server Name: " & Rs.Fields ("name")
dp "Server DN: " & Rs.Fields ("distinguishedName")
strServerAGList = strServerAGList & ";" & Rs.Fields ("name")
Rs.MoveNext
Wend
strServerAGList = Mid (strServerAGList, 2)
Call FinishLDAPQuery (Rs)
dp "Exchange server list for AG " & arr (i) & ": " & strServerAGList
Next
End Sub
'
' GetStoresForStorageGroup
' Purpose:
' Prepare a semi-colon separated list of all the message stores
' in a given storage group. A parallel list containing the type
' of the storage group is also prepared. This routine uses ADSI
' enumeration to obtain the list.
'
' Inputs:
' strStorageGroup - contains the distinguished name of the storage
' group to be examined.
'
' Outputs:
' Set the value of the global variables strStoreDN and strStoreType
'
' Requires:
' None
'
Sub GetStoresForStorageGroup (strStorageGroup)
Dim objSG, objStore
Dim strOC, strType
strStoreDN = ""
strStoreType = ""
Set objSG = GetObject ("LDAP://" & strStorageGroup)
For Each objStore in objSG
strOC = objStore.Get ("objectCategory")
' turn objectCategory into objectClass
strType = Left (strOC, Instr (strOC, ",") - 1)
strType = Replace (Mid (strType, 4), "-", "")
dp vbTab & "store: " & objStore.Name & _
" " & strType
strStoreDN = strStoreDN & ";" & objStore.Get ("distinguishedName")
strStoreType = strStoreType & ";" & strType
Next
strStoreDN = Mid (strStoreDN, 2)
strStoreType = Mid (strStoreType, 2)
' Report results
dp "store DN's: " & strStoreDN
dp "store Types: " & strStoreType
End Sub
'
' GetStoresForStorageGroupLDAP
' Purpose:
' Prepare a semi-colon separated list of all the message stores
' in a given storage group. A parallel list containing the type
' of the storage group is also prepared. This routine uses LDAP
' queries to obtain the list.
'
' Inputs:
' strStorageGroup - contains the distinguished name of the storage
' group to be examined.
'
' Outputs:
' Set the value of the global variables strStoreDN and strStorType
'
' Requires:
' None
'
Sub GetStoresForStorageGroupLDAP (strStorageGroup)
Dim strQuery, strType
strStoreDN = ""
strStoreType = ""
' public stores first
strType = "msExchPublicMDB"
strQuery = "<LDAP://" & strStorageGroup & ">;" & _
"(objectCategory=msExchPublicMDB);" & _
"name,cn,distinguishedName;" & _
"onelevel"
Call DoLDAPQuery (strQUery, Rs)
While Not Rs.EOF
strStoreDN = strStoreDN & ";" & Rs.Fields ("distinguishedName")
strStoreType = strStoreType & ";" & strType
dp vbTab & "store: " & Rs.Fields ("name") & _
" " & strType
Rs.MoveNext
Wend
Call FinishLDAPQuery (Rs)
' private stores next
strType = "msExchPrivateMDB"
strQuery = "<LDAP://" & strStorageGroup & ">;" & _
"(objectCategory=msExchPrivateMDB);" & _
"name,cn,distinguishedName;" & _
"onelevel"
Call DoLDAPQuery (strQUery, Rs)
While Not Rs.EOF
strStoreDN = strStoreDN & ";" & Rs.Fields ("distinguishedName")
strStoreType = strStoreType & ";" & strType
dp vbTab & "store: " & Rs.Fields ("name") & _
" " & strType
Rs.MoveNext
Wend
Call FinishLDAPQuery (Rs)
strStoreDN = Mid (strStoreDN, 2)
strStoreType = Mid (strStoreType, 2)
' Report results
dp "store DN's: " & strStoreDN
dp "store Types: " & strStoreType
End Sub
'
' GetStorageGroupsForServer
' Purpose:
' Prepare a semi-colon separated list containing the storage
' groups defined on a specific Exchange server.
'
' Inputs:
' strServerDN - the distinguished name of the server for
' which the storage group list is desired.
'
' Outputs:
' Set the value of the global variables strServerSG and
' strServerSGDN.
'
' Requires:
' None
'
Sub GetStorageGroupsForServer (strServerDN)
Dim strQuery
strQuery = "<LDAP://" & "CN=InformationStore," & strServerDN & ">;" & _
"(objectCategory=msExchStorageGroup);" & _
"name,cn,distinguishedName;" & _
"onelevel"
strServerSG = ""
strServerSGDN = ""
Call DoLDAPQuery (strQuery, Rs)
dp "All storage groups on server " & Left (strServerDN, InStr (strServerDN, ",") - 1)
While Not Rs.EOF
' output the current server found
dp "Storage Group CN: " & Rs.Fields ("cn")
dp "Storage Group Name: " & Rs.Fields ("name")
dp "Storage Group DN: " & Rs.Fields ("distinguishedName")
strServerSG = strServerSG & ";" & Rs.Fields ("name")
strServerSGDN = strServerSGDN & ";" & Rs.Fields ("distinguishedName")
Call GetStoresForStorageGroup (Rs.Fields ("distinguishedName"))
Rs.MoveNext
Wend
strServerSG = Mid (strServerSG, 2)
strServerSGDN = Mid (strServerSGDN, 2)
Call FinishLDAPQuery (Rs)
' Report our results
dp "All Storage Groups on Server: " & strServerSG
End Sub
'
' GetStorageGroupsForAllServers
' Purpose:
' This is simply a sample of how to process all of the servers,
' storage groups, and information stores in a given Exchange
' organization.
'
' Inputs:
' None
'
' Outputs:
' None relevant
'
' Requires:
' Must be called after GetAllServers.
'
Sub GetStorageGroupsForAllServers
Dim arr, arrSG
Dim i, j
arr = Split (strServerListDN, ";")
For i = LBound (arr) To UBound (arr)
Call GetStorageGroupsForServer (arr (i))
arrSG = Split (strServerSGDN, ";")
For j = LBound (arrSG) To UBound (arrSG)
Call GetStoresForStorageGroupLDAP (arrSG (j))
Next
Next
End Sub