Getting Mailbox Information from Exchange 2003 Cluster
I was recently tinkering with Visual Basic Script that would obtain the user mailbox information from the Exchange 2003 cluster. In particular I was interested in getting mailbox information stored in Active Directory as well as obtaining current mailbox status (such as current size of the mailbox).
As my VB script skills are dated the first thing was to gather basic resources such as:
Visual Basic Script documentation help file available in
“Windows Script 5.6 Documentation “
Scriptomatic tool for generating WMI scripts https://www.microsoft.com/downloads/details.aspx?familyid=09dfc342-648b-4119-b7eb-783b0f7d1178&displaylang=en
ADSI Scriptomatic for generating ADSI scripts https://www.microsoft.com/technet/scriptcenter/tools/admatic.mspx
Querying Cluster Information
In general the cluster name may or may not be associated with the node that has Exchange resources so we cannot send the query to the cluster name. Also it is common to see Active-Active-Pasive Exchange clusters where the mailboxes are partitioned among the active nodes. To ensure we get complete list of all mailboxes we should query all active nodes with Exchange resources.
Armed with the Scriptomatic we quickly see that we can make a WMI query on the MSCluster WMI class to obtain the list of all nodes of the (Windows) cluster. We can then check the status of these nodes and return in the array the list of active nodes. Here we use the semi-synchronous call that works faster especially for large result sets (we will appreciate it later when we query for mailbox status). Essentially we can start enumerating objects before the entire result set is available-you can find further detail in
“Making a Semisynchronous Call with VBScript”
https://msdn2.microsoft.com/en-us/library/aa392301(VS.85).aspx
In the WMI query we are getting all the cluster nodes nodes and we are specifying "WQL" as the type of the query language (I think the only one available still with WMI) so that we can get to specify flags required for semi-synchronous call.
'Checks for cluster configuration if available. In non-clustered information will return empty array
' WMI query is issued against the cluster name to return the list of the nodes of the cluster. We then
' add to dymanic array nodes that are active
Function GetClusterNodes(cluster)
Dim activeNodes()
ReDim activeNodes (0)
Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20
'On Error Resume Next
WScript.Echo "Getting cluster configuration from " & cluster
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & cluster & _
"\root\mscluster")
Set colItems = objWMIService.ExecQuery ("Select * from MSCluster_Node", "WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
For Each objItem in colItems
If CInt(objItem.State)=0 Then
activeNodes(UBound(activeNodes))=objItem.Name
ReDim Preserve activeNodes(UBound(activeNodes)+1)
End If
WScript.Echo "Name: " & objItem.Name
WScript.Echo "State: " & objItem.State
WScript.Echo "Status: " & objItem.Status
WScript.Echo "Roles: " & objItem.Roles
WScript.Echo "-----------------------------------------------------"
Next
ReDim Preserve activeNodes(UBound(activeNodes)-1)
WScript.Echo "Active Nodes are:"
For Each node in activeNodes
WScript.Echo "Node=" & node
Next
Set objWMIService = Nothing
GetClusterNodes = activeNodes
End Function
The alternative to this approach would be to query for NodeToActiveGroup class.The advantage is that it returns the active nodes of the cluster and that it allows us to check the cluster Resource Groups so we could try to get Active nodes with Exchange resources. However, because strings are returned parsing is more complex. Another issue is that in Cluster Administrator tool the users can change the name of the Resource Group so it is problematic to do a test on the PartComponent property for Exchange resource groups. The code below does not to it and prevents adding the same node twice (once for OS resource group and second time for Exchange resource group).
' Query the cluster for the list of active nodes. Note we parse out the name of the node from
' the GroupComponent property. We test is node has not been added already as same node appears
' multiple times (for each cluster resource). As nodes are added we
' we resize the activeNodes array dymamically.
Function GetActiveClusterNodes (cluster)
'On Error Resume Next
Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20
WScript.Echo "Querying cluster " & cluster & " for active Exchange nodes..."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & cluster & "\root\MSCluster")
Set colItems = objWMIService.ExecQuery("SELECT * FROM MSCluster_NodeToActiveGroup", "WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
Dim activeNodes()
ReDim activeNodes (0)
label="MSCluster_Node.Name="
For Each objectitem In colItems
groupComponent = Trim(objectitem.GroupComponent)
WScript.Echo "Group=" & objectitem.GroupComponent & " Part=" & objectitem.PartComponent
If InStr(groupComponent,label)>0 Then
node = Mid ( groupComponent, Len(label)+2, Len(groupComponent)-Len(label)-2 )
found = False
For Each n in activeNodes
If StrComp( n, node)=0 Then
found=True
Exit For
End If
Next
If found = False Then
activeNodes(UBound(activeNodes)) = node
ReDim Preserve activeNodes(UBound(activeNodes)+1)
End If
End If
Next
ReDim Preserve activeNodes(UBound(activeNodes)-1)
WScript.Echo "Active Nodes detected:"
For Each node in activeNodes
WScript.Echo node
Next
Set objWMIService = Nothing
GetActiveClusterNodes = activeNodes
End Function
With all this it seems that first version is better because we cannot guarantee that the Administrator will not renaming resources in Cluster Administrator tool. We simply will then query all the active nodes, and for the active nodes with no Exchange resources we will simply not see any mailboxes returned. Assuming CLUSTER_NAME holds the name of the cluster we iterate as follows
Dim EXCHANGE_SERVER_NODES
EXCHANGE_SERVER_NODES = GetClusterNodes(CLUSTER_NAME)
For Each server In EXCHANGE_SERVER_NODES
WScript.Echo "Querying mailbox status on node " & server
GetMailboxStatus(server)
WScript.Echo Now()
Next
Getting Mailbox Status information
So now we can put together a function to return us the mailboxes for a given machine name. In this case we skip all the system mailboxes as we are interested just in "ordinary" users. Unlike with ADSI there is no easy way of doing the filtering in the WQL so we have to filter in code in this case.
'Uses WMI interface to query the Exchange node 'server' for mailbox status information.
'Note that the query will return ALL mailboxes (included system mailboxes and mailboxes to be
'deleted or reassigned to a different user.
'The subroutine will ignore all system mailboxes
Sub GetMailboxStatus(server)
Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & server & _
"\ROOT\MicrosoftExchangeV2")
Set colItems = objWMIService.ExecQuery ("Select * from Exchange_Mailbox","WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
k=0
total=0
For Each objItem in colItems
total=total+1
'Exclude System Mailboxes
If InStr(UCase(objItem.LegacyDN), "CN=SYSTEMMAILBOX") = 0 And InStr(UCase(objItem.LegacyDN), "CN=SMTP") = 0 _
And InStr(UCase(objItem.LegacyDN), "CN=MICROSOFT SYSTEM ATTENDANT")=0 Then
k=k+1
WScript.Echo k & " MailboxName=" & objItem.MailboxDisplayName & " StorageLimitInfo="& objItem.StorageLimitInfo
WScript.Echo " Server Name=" & objItem.ServerName & " StorageGroup=" & objItem.StorageGroupName & " Store Name=" & objItem.StoreName
End If
Next
WScript.Echo " Detected " & k & " user mailboxes (total="& total &"). Skipped " & total - k & " system mailboxes."
Set objWMIService = Nothing
End Sub
Querying Mailbox information in Active Directory using ADSI
Now that we have the MailBox status information we turn to querying for user mailbox information stored in Active Directory. The default queries are limited to results sets of just 1000 object. In order to process large result sets we have to implement a paged query. This is achieved by specifying "Page Size" property on the Command object. Now the server will return to client data in chunks having at most Page Size objects. The paging mechanism is transparent to the client so no special code to handle paging is required.
“Retrieving Large Results Sets” https://msdn2.microsoft.com/en-us/library/aa746459(VS.85).aspx has more information about this topic. It is also recommended not to cache the results so we set the "Cache Results" property to False.
Our query uses a filter to return the user mailboxes skipping any System Mailbox as we do not want to process them in this case.
'Queries Active Directory for mailbox configuration. The query will list only user mailboxes, skipping system mailboxes. Sub GetMailBoxes()
Const DOMAIN_CONTROLLER = "NoddyDCGC"
Const DOMAIN_LDAP = "DC=noddy,DC=com"
strADsPath = "LDAP://" & DOMAIN_CONTROLLER & "/" & DOMAIN_LDAP
'Open connection to AD
Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open "Provider=ADsDSOObject;"
Set objCommand = CreateObject("ADODB.Command")
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 500
objCommand.Properties("Timeout") = 10 ' Seconds
objCommand.Properties("Cache Results") = False
'query for user object in Active Directory
objCommand.CommandText = "<" & strADsPath & ">" & ";(&(objectClass=user)(homeMDB=*)(!CN=SystemMailbox{*}))" & ";distinguishedName,name" & ";subtree"
'Execute search to get Recordset
Set objMailboxRS = objCommand.Execute
total=0
While Not objMailboxRS.EOF
'Bind to mailbox object for the current user
Set objMailbox = GetObject("LDAP://" & DOMAIN_CONTROLLER &"/" & objMailboxRS.Fields("distinguishedName") )
Set objStore = GetObject("LDAP://" & DOMAIN_CONTROLLER &"/" & objMailbox.homeMDB)
'Update mailbox information in Database
WScript.Echo "Mailbox " & objMailbox.name & " is stored in " & objStore.cn
total=total+1
objMailboxRS.MoveNext
Wend 'End While EOF
WScript.Echo "Detected " & total & " user mailboxes."
objMailboxRS = null
End Sub
Differences between mailbox numbers returned by WMI and ADSI
In general, the number of mailboxes returned by querying Active Directory using ADSI interface will be different from that reported by WMI query. This discrepancy can occur for example if a user is deleted from Active Directory but the associated mailbox has not yet been purged (or reassigned to a different user). Also the opposite may occur where a user account has already been created but the mailbox has not yet been created (because he never accessed it nor received yet and email).
Comments
- Anonymous
April 16, 2008
PingBack from http://microsoftnews.askpcdoc.com/?p=2823