Automating Exchange 2000 Management with Windows Script Host

Archived content. No warranty is made as to technical accuracy. Content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

By Alain Lissoir, Hewlett Packard Consultancy & Integration (HPCI) - Technology Leadership Group (TLG), Brussels, Belgium

The purpose of this white paper is to demonstrate scripting techniques for Microsoft® Exchange 2000 Server management, using Windows Script Host (WSH) and Exchange 2000 components.

The document presents two advanced WSH scripts based on the new Exchange 2000 COM technologies. These scripts perform automated management tasks in a Microsoft® Windows® 2000 and Exchange 2000 environment.

On This Page

Introduction
Scriptable Windows 2000 Components
Exchange 2000 Components Accessible with Scripts
Exchange 2000 Architecture Overview
Logical View of Exchange 2000 COM Components
Exploring the Exchange 2000 COM Logical View with Scripts
Advanced Script Samples
Conclusion
Appendix

Introduction

System management is important for ensuring system availability. In the early days of networked PCs, managing a system consisted of simple tasks such as making backups and monitoring free disk space. These early systems ran only 10 or 20 users on a local LAN at 1 megabit per second. A single administrator was able to manage a system easily.

As technology advances, administrators face increasingly complicated tasks. Current systems must be up and running 24 hours a day, 365 days a year. Many companies provide e-mail systems that are connected to the external world. With the growth of the Internet and e-commerce, administrators are now responsible for thousands of users distributed on different systems around the world.

This evolution demands that network operating systems offer more features to address business needs. Microsoft® Exchange 2000 Server addresses some of these needs, but large infrastructures continue to ask for added functionality.

Administrators today do most of their administration with automated tasks. Because an administrator is not a developer, automation methods should be easy to use. Scripting languages such as JavaScript, Microsoft® Visual Basic®, Scripting Edition (VBScript), and Perl are quick and effective ways to write logic for network operating systems. Therefore, system structures should be accessible from the scripting environment.

With the release of Microsoft® Windows® 2000 and Exchange 2000, Microsoft has provided an enormous set of new features accessible from the scripting environment. These features enable you to use, create, and extend the basic management functions included in the products. Functions are accessed through scriptable COM objects that are available from any automation language.

This white paper illustrates how Exchange 2000 technologies can be helpful for management purposes. It examines existing scriptable COM technologies and provides server-centric samples developed under Windows Script Host (WSH) to help users with Exchange 2000 management tasks.

Note To automate Exchange 2000 with scripts, you should have a working understanding of the following technologies:

  • Windows 2000 global architecture

  • Microsoft® Active Directory® directory service

  • Exchange 2000 global architecture

  • VBScript or JavaScript, especially from the WSH environment

  • Active Directory Service Interfaces (ADSI)

  • Windows Management Instrumentation (WMI)

  • Microsoft ActiveX® Data Objects (ADO)

Scriptable Windows 2000 Components

Exchange 2000 is tightly integrated with Windows 2000. Because a Windows 2000 network is the foundation for Exchange 2000, it is important to understand which components of Windows 2000 are involved with Exchange 2000. Windows 2000 Active Directory, Windows Management Instrumentation (WMI), and Internet Information Services (IIS) are some examples of Windows 2000 components used by Exchange 2000. From Windows 2000, three important COM technologies are essential for Exchange 2000 management: Active Directory Service Interfaces (ADSI), WMI, and ActiveX Data Objects (ADO).

Active Directory Service Interfaces

ADSI is a COM scriptable technology that is part of Windows 2000 Professional and Windows 2000 Server. ADSI is an application programming interface (API) into Active Directory that enables applications to access, create, and modify Active Directory objects. For example, ADSI allows an administrator to write a script to create user objects in bulk in Active Directory. Managing Exchange 2000 often requires the use of ADSI because Active Directory is the underlying directory for Exchange 2000. For more information about the ADSI.User object class, see https://microsoft.com/windows2000.

Windows Management Instrumentation

Windows Management Instrumentation (WMI) is the Microsoft implementation of Web-based Enterprise Management (WBEM). WMI provides a uniform way of accessing information on Windows systems. WMI enables systems, applications, networks, and other managed components to be represented using the Common Information Model (CIM). Because Exchange 2000 exposes management information through WMI and relies on the base features available in Windows 2000, WMI is a great way to manage server information such as CPU usage per process, disk space, available memory, Exchange connector state, queue information, Exchange server state, Exchange services, store file sizes, and so on. WMI offers a scripting interface that provides easy access to management information. For more information about WMI and its architecture, see the MSDN Library at https://msdn.microsoft.com/library.

ActiveX Data Objects

ADO enables client applications to access and manipulate data from a database server through an OLE DB provider. Under Exchange 5.5, the Information Store is not accessible with ADO because Exchange 5.5 does not have an OLE DB provider. Exchange 2000 implements an OLE DB provider for the Exchange store, also known as the Web Storage System. ADO 2.5 should be used to access Exchange 2000 stores. For more information about ADO and its architecture, see the MSDN Library at https://msdn.microsoft.com/library.

Exchange 2000 Components Accessible with Scripts

The installation of Exchange 2000 adds features and modifies existing features in the Windows 2000 base operating system. Some of these changes are dedicated to Exchange 2000 management.

Active Directory Schema

The first Exchange 2000 installation in an organization creates a set of new classes and properties in Microsoft Active Directory to support Exchange 2000-specific objects. This change is realized throughout the organization. After the schema is modified, the Exchange 2000 installation process creates a specific container in the Active Directory Configuration Naming Context to hold the configuration data of the Exchange organization. For more information about Active Directory and Active Directory schema, see the MSDN Library at https://msdn.microsoft.com/library.

Exchange WMI Providers

Exchange 2000 adds three new WMI providers to enhance Exchange manageability. These providers create a new namespace in the WMI Common Information Model (CIM) repository and add five new WMI classes. Each class is related to a different part of Exchange 2000.

  • The WMI Exchange Routing Table provider creates the ExchangeServerState and ExchangeConnectorState classes.

  • The WMI Exchange Queue provider creates the ExchangeLink and ExchangeQueue classes.

  • The WMI Exchange Cluster provider creates the ExchangeClusterResource class.

Two more WMI providers are added with Exchange 2000 Server Service Pack 2.

  • The WMI Exchange Message Tracking provider creates the Exchange_MessageTrackingEntry class.

  • The WMI Exchange DS Access provider creates the Exchange_DSAccessDC class.

For more information about the WMI Exchange providers, see "An Overview of Exchange 2000 WMI Providers" in the Appendix.

CDO for Exchange 2000

CDO for Exchange 2000 (CDOEX) is a new version of CDO built to use specific features related to Exchange 2000, such as Internet Standards and the Exchange store (through the Exchange OLE DB provider). CDOEX relies only on the use of Internet standard protocols and does not use MAPI. When Exchange 2000 is installed, CDO for Windows 2000 (CDOSYS) is upgraded to CDOEX. CDO for Exchange 2000 is a superset of CDO for Windows 2000. The difference is that CDOEX brings additional functionality related to Exchange 2000.

For more information about CDOSYS and CDOEX, see the MSDN Library at https://msdn.microsoft.com/library.

CDO for Exchange Management

CDO for Exchange Management (CDOEXM) provides objects and interfaces for the management of many Exchange 2000 components. For example, CDOEXM can configure Exchange servers and stores, mount and dismount stores, and create and configure mailboxes. This document examines the various capabilities of CDOEXM.

CDOEXM acts as an extension for ADSI and CDOEX to make it easier to create and access mailbox definitions stored in Active Directory and in the stores. At the server level, CDOEXM allows you to retrieve specific information about the server itself, as well as about storage groups and their contents. CDOEXM is an important companion to ADSI and CDOEX when working with Exchange 2000.

For more information about CDOEXM, see "Exchange 2000 Architecture Overview," later in this document.

Exchange OLE DB Provider

OLE DB allows applications to uniformly access data stored in diverse information sources. It supports the amount of Database Management System (DBMS) functionality appropriate to the data source, enabling it to share its data.

Exchange 2000 supports OLE DB for Documents interfaces. OLE DB for Documents is a collection of OLE DB interfaces that allow applications to traverse folders and documents, using Uniform Resource Locators (URLs). OLE DB for Documents is the preferred method for applications to access the Exchange store.

There are two ways in which Exchange 2000 provides OLE DB 2.5 support. The first is by using Microsoft Internet Publishing Provider (MSDAIPP), which accesses Exchange 2000 Server using World Wide Web Distributed Authoring and Versioning (WebDAV).

The second way in which Exchange 2000 provides OLE DB 2.5 support is by having a native OLE DB 2.5 provider for the Exchange store. The primary purpose of this provider is to achieve better performance than is possible over WebDAV. This provider accesses the Exchange store directly (through COM) rather than making a round trip through WebDAV. Office 2000, CDO for Exchange 2000, and CDO for Exchange Management use the OLE DB COM provider.

For more information about the Exchange OLE DB provider and the Exchange store, see the MSDN Library at https://msdn.microsoft.com/library.

Installable File System

The Exchange store can also be accessed through the Installable File System (IFS). The Exchange store exists as a file system folder on drive M of the Exchange 2000 server. Drive M can be shared like any other drive.

The default names for the first public and mailbox stores are respectively "Public Folders" and "MBX". There is no COM abstraction mechanism to access properties for an item in IFS directly. To access item properties with a COM abstraction technique, use the WebDAV protocol, the Exchange OLE DB (ExOLEDB) provider with ADO 2.5, CDO for Exchange 2000 (CDOEX), or the Messaging API (MAPI).

For more information about NTFS and the Installable File System, see the Exchange SDK at https://msdn.microsoft.com/library.

Exchange 2000 Architecture Overview

Exchange 2000 offers various technologies to access its components. Figure 1 represents a simplified view of the interaction and relationships between these technologies.

Figure 1 Exchange store COM interfaces

Figure 1 Exchange store COM interfaces

There are two methods of client/server access to the Exchange store. For application creation and management, the Exchange store can be accessed with CDO 1.21 through MAPI. This method provides backward compatibility. The second method uses WebDAV and is based on Internet standard protocols as opposed to MAPI, which uses Remote Procedure Calls (RPC). WebDAV is the recommended method.

Note Figure 1 does not include WMI components because there is no direct interaction between WMI and the Exchange store. See "An Overview of Exchange 2000 WMI Providers" in the Appendix for more information about Exchange 2000 WMI providers. IFS is not represented because it is a file system component of the Exchange store, rather than a COM component.

If the application is running locally on an Exchange 2000 server, the Exchange store can also be accessed through the Exchange OLE DB (ExOLEDB) provider.

Another way to access the Exchange store and abstract the Exchange OLE DB provider is to use ADO 2.5. With the help of the ExOLEDB provider, ADO 2.5 offers a navigation model that allows exploration of the Exchange store. This is the easiest COM technology to use to explore mailboxes and public folder hierarchies.

ADO 2.5 implements features such as:

  • The GetChildren method to return a RecordSet of items in a folder (see Sample 21).

  • The MoveRecord and CopyRecord methods to allow move and copy operations in a folder tree.

ADO 2.5 does not allow you to examine the content of an item. CDOEX, however, implements the business logic of collaboration-related items such as e-mail messages, folders, calendaring items, contacts, and so on. ADO and CDOEX are designed to work together. ADO explores the folder tree, and CDOEX manipulates item content.

On top of CDOEX, Exchange 2000 implements CDO for Exchange Management (CDOEXM). CDOEXM is a COM technology to facilitate management tasks. CDOEXM uses objects instantiated with ADSI and CDOEX to manage operations between the stores and Active Directory. There is an important distinction between the purpose of CDOEXM and the purpose of CDOEX. CDOEXM is made for the management of Exchange 2000 component containers: servers, stores, and mailboxes. CDOEX manipulates the content of these containers: messages, contacts, calendar items, and so on.

The admin logic layer implements business logic related to administration so that CDOEXM and the Exchange System Manager (ESM) user interface exhibit consistent behavior. The admin logic layer is not accessible from the application level.

CDOEX provides a convenient object model for managing folders, messages, appointments, contacts, and other items, as well as the properties of those items. CDOEX accesses data through OLEDB 2.5 interfaces and is specifically designed to operate with the Exchange store.

CDOEX integrates with ADO 2.5 to provide a consistent data-access interface to the Exchange store and Active Directory. When assigning values to CDOEX properties, CDOEX saves the data to the correct location (either the Exchange store or Active Directory).

CDOEX objects such as Folder, Person, and many others provide a Fields collection on the default interface, allowing direct access to the properties of items in the Exchange store. See the Exchange SDK for more information about the Fields collection.

CDOEX properties provide essential functionality for collaborative applications using the Exchange store. There are situations, however, when using ADO (see Sample 21) or OLE DB may be more appropriate.

When an item is accessed using an ADO Record object, all of its data, including the stream, is presented in a Fields collection. The stream, which is defined in the ADO 2.5 specification, is accessed using the ADO Stream object from the GetStream method or from within the Fields collection (see Sample 6 to explore the content of the stores and associated Fields collection). However, ADO assumes no correlation between the item properties and the default stream.

CDOEX and ADSI objects are designed to work in tandem. You can use CDOEX or ADSI objects to retrieve information about users and their Exchange 2000 mailboxes (see Sample 11 with ADSI and Sample 13 with CDOEX). You can use the same logic to create or delete users and their mailboxes, and to manage user and contact information in Active Directory.

Figure 2 Windows 2000 Active Directory COM interfaces

Figure 2 Windows 2000 Active Directory COM interfaces

Configuration information resides primarily in Active Directory. ADSI is a generic API into Active Directory that has no functions specifically for managing Exchange data in Active Directory. ADSI cannot access data in the Exchange store.

CDOEXM encapsulates and simplifies Exchange 2000 management tasks so that data in Active Directory and resources in the Exchange store can be managed. CDOEXM relies on the admin logic layer, which contains business logic for Exchange 2000 management. There is usually no need to work at the granular level of Active Directory using ADSI unless CDOEXM does not implement logic for a particular management task.

In the same way, CDOEX has no specific function for managing Active Directory or the Exchange data contained in Active Directory. CDOEX uses ADSI and CDOEXM to perform tasks in both Active Directory and the Exchange store. The following paragraph provides an example.

To create a user with an associated mailbox on an Exchange 2000 server, an ADSI.User object must first be instantiated to create the user in Active Directory. The piece of code then uses CDOEXM to create the user's mailbox in the Exchange store (see Sample 10 and Sample 11). Existing mailboxes are retrieved with the same process. With CDOEX, the logic is exactly the same (see Sample 12 and Sample 13), but in this case a CDO.Person object is instantiated.

When CDOEXM creates an Exchange mailbox (or retrieves mailbox information), it automatically sets (or gets) the properties in the Exchange store to associate the mailbox with the user in Active Directory. CDOEXM acts as an extension to ADSI and CDOEX.

Logical View of Exchange 2000 COM Components

This section presents a logical view of the various COM technologies used to retrieve and manage information in Exchange 2000.

The logical structure of Exchange 2000 is hierarchical. The server, which is at the top of the hierarchy, contains storage groups. Storage groups contain mailbox stores and public stores. Mailbox stores contain mailboxes, and mailboxes contain folders. Public stores contain folders as well. Mailbox and public folders hold various types of objects (documents, schedule information, messages, and so on). These objects can be further divided into parts. For example, body text and attachments are parts of an e-mail message.

Exchange 2000 uses a specific technology to access data at each level. To manage Exchange 2000 with scripts, you should understand which COM technology to use to access each kind of data. Figure 3 maps the Exchange 2000 object hierarchy with COM technologies and their uses.

An Exchange 2000 server can have several storage groups, each of which can contain many mailbox and public stores. For the sake of simplicity, Figure 3 shows only one storage group, one mailbox store, and one public store. On the left and right sides, large vertical arrows show the paths from the organization level to the message level of the hierarchy. Each arrow represents an access method: ADSI is on the left with steps marked with numbers and CDOEX is on the right with steps marked with letters. The smaller numbered and lettered arrows connect each Exchange 2000 component with the technology or technologies (WMI, CDOEX, ADSI, and so on) used to access it.

You can retrieve information from each COM component at each level of this logical view. Later in this document, the EnumE2KinXL.wsf script is presented piece by piece to show you how to do this. Sample 6 through Sample 24 constitute the complete EnumE2KinXL.wsf script.

Figure 3 Exploring the Exchange 2000 COM architecture

Figure 3 Exploring the Exchange 2000 COM architecture

The next section explores the Exchange 2000 object hierarchy using sample scripts. The purpose of the section is to increase your understanding of Exchange 2000 COM technologies and their interactions.

COM objects with Exchange 2000 management capabilities are marked and summarized as "Management Points."

Exploring the Exchange 2000 COM Logical View with Scripts

Retrieving the List of Exchange 2000 Servers in the Organization

To manage systems effectively, administrators need information updated constantly. For Exchange 2000 administrators, this might mean retrieving a list of Exchange servers in the organization and information about those servers.

One way to do this is with an ADSI query in the Active Directory Configuration Naming Context, but ADSI does not contain business logic specifically related to Exchange 2000 servers. With ADSI, a script must retrieve server information property by property from Active Directory. This presents a problem: How does the script know which properties to read and how to interpret their values? Another problem is that ADSI doesn't provide real-time status or "health" information about the Exchange 2000 server. (That is, is it up or down?) Retrieving information with WMI solves these problems.

Using WMI to Retrieve Exchange 2000 Server States

This section refers to points 1 and A in Figure 3. 

Exchange 2000 Setup adds three providers to the WMI Common Information Model (CIM) Repository. The first is the WMI Exchange Routing Table provider, which defines the following WMI classes:

  • ExchangeServerState: This class retrieves information about the status of relevant services, including memory used, processor type, disk space available, and the mail queue of Exchange 2000 servers in an organization. The ExchangeServerState class works in conjunction with the monitoring configuration defined by the Exchange System Manager (ESM).

  • ExchangeConnectorState: This WMI class retrieves information about the Exchange 2000 mail connectors.

Both classes retrieve their information from the routing table. This information is available to administrators on any Exchange 2000 server in an organization. For more information about the Exchange Routing Table provider, see "The Exchange 2000 Routing Table Provider" in the Appendix.

The second WMI provider is the Exchange Queue provider, which defines the following WMI classes:

  • ExchangeQueue: This class retrieves information through the queue API about the existing queues on an Exchange 2000 server.

  • ExchangeLink: This class retrieves information through the queue API about the existing links on an Exchange 2000 server.

For more information about the Exchange Queue provider, see "The Exchange 2000 WMI Queue Provider."

Finally, Exchange 2000 adds the WMI Exchange Cluster provider, which defines the following WMI class:

  • ExchangeClusterResource: This class retrieves information about Exchange 2000 clustered resources through the cluster API.

For more information about the ExchangeClusterResource provider, see "The Exchange 2000 WMI Cluster Provider" in the Appendix.

The script samples in the next section demonstrate how to use these three WMI providers from WSH. The first sample uses the ExchangeServerState WMI class and will be reused later as the basis for a more complex script (EnumE2KinXL.wsf).

Using ExchangeServerState from Windows Script Host

Sample 1 shows how the ExchangeServerState WMI class can be used from WSH. Lines 11 to 13 define three constants to be used in the WMI moniker (lines 24 to 26). The cComputerName constant is the Exchange 2000 server to be contacted. The cWMINameSpace is the Exchange 2000 WMI namespace. The cWMIinstance constant provides the class name to be instantiated at lines 24 to 26, in this case the ExchangeServerState class.

Sample 1 Using the ExchangeServerState WMI class 

   1:' VBScript script listing all ExchangeServerState names and properties       
   2:' available with the WMI Exchange 2000 provider.  
   .: 
   9:Option Explicit 
  10: 
  11:Const cComputerName = "LocalHost" 
  12:Const cWMINameSpace = "root/cimv2/applications/exchange" 
  13:Const cWMIInstance  = "ExchangeServerState" 
  ..: 
  ..: 
  24:Set ExchangeServerList = _ 
                  GetObject("winmgmts:{impersonationLevel=impersonate}!//" &  
  25:                       cComputerName & "/" & _ 
  26:                       cWMINameSpace).InstancesOf(cWMIInstance) 
  27: 
  28:For each ExchangeServer in ExchangeServerList 
  29:    WScript.Echo "---------------------------------------------" 
  30:    WScript.Echo "Name: " & ExchangeServer.Name 
  31:    WScript.Echo "DN: " & ExchangeServer.Dn 
  32:    WScript.Echo "GUID: " & ExchangeServer.Guid 
  33:    WScript.Echo "Version: " & ExchangeServer.Version 
  34:    WScript.Echo "GroupDN: " & ExchangeServer.GroupDN 
  35:    WScript.Echo "Unreachable: " & ExchangeServer.Unreachable 
  36: 
  37:    WScript.Echo "ServerMaintenance: " & ExchangeServer.ServerMaintenance 
  38: 
  39:    WScript.Echo "ServerStateString: " & ExchangeServer.ServerStateString 
  40:    WScript.Echo "ServerState: " & ExchangeServer.ServerState 
  41: 
  42:    WScript.Echo "QueuesStateString: " & ExchangeServer.QueuesStateString 
  43:    WScript.Echo "QueuesState: " & ExchangeServer.QueuesState 
  44: 
  45:    WScript.Echo "DisksStateString: " & ExchangeServer.DisksStateString 
  46:    WScript.Echo "DisksState: " & ExchangeServer.DisksState 
  47: 
  48:    WScript.Echo "MemoryStateString: " & ExchangeServer.MemoryStateString 
  49:    WScript.Echo "MemoryState: " & ExchangeServer.MemoryState 
  50: 
  51:    WScript.Echo "CPUStateString: " & ExchangeServer.CPUStateString 
  52:    WScript.Echo "CPUState: " & ExchangeServer.CPUState 
  53: 
  54:    WScript.Echo "ClusterStateString: " & _ 
                      ExchangeServer.ClusterStateString 
  55:    WScript.Echo "ClusterState: " & ExchangeServer.ClusterState 
  56: 
  57:    WScript.Echo "ServicesStateString: " & _ 
                      ExchangeServer.ServicesStateString 
  58:    WScript.Echo "ServicesState: " & ExchangeServer.ServicesState 
  59:Next 
  ..: 
  ..: 
  ..:

After the instantiation is complete, the returned object is a collection of Exchange servers, known in cComputerName. The "For Each" loop (lines 28 to 59) enumerates all the servers in this collection.

For each server found, a set of properties and their states is retrieved from the Exchange 2000 routing table (lines 30 to 58) with the help of the Exchange Routing Table WMI provider. The routing table includes data about all of the servers and connectors in the organization. The ExchangeServerState class reads this data.

Using ExchangeConnectorState from WSH

To retrieve connector states with the ExchangeConnectorState class, substitute the ExchangeConnectorState class name in line 13 (see Sample 2). This script retrieves an organization-wide view of all the connectors and their states, as seen by the cComputerName used to instantiate the class.

Sample 2 Using the ExchangeConnectorState *** *** WMI class 

   1:' VBScript script listing all ExchangeConnectorState names and properties     
   2:' available with the WMI Exchange 2000 provider.  
   .: 
   9:Option Explicit 
  10: 
  11:Const cComputerName = "LocalHost" 
  12:Const cWMINameSpace = "root/cimv2/applications/exchange" 
  13:Const cWMIInstance  = "ExchangeConnectorState" 
  ..: 
  ..: 
  24:Set ExchangeConnectorList = _ 
                   GetObject("winmgmts:{impersonationLevel=impersonate}!//"& 
  25:                        cComputerName & "/" & _ 
  26:                        cWMINameSpace).InstancesOf(cWMIInstance) 
  27: 
  28:For each ExchangeConnector in ExchangeConnectorList 
  29:    WScript.Echo "---------------------------------------------" 
  30:    WScript.Echo "DN: " & ExchangeConnector.Dn 
  31:    WScript.Echo "Name: " & ExchangeConnector.Name 
  32:    WScript.Echo "GUID: " & ExchangeConnector.Guid 
  33:    WScript.Echo "GroupDN: " & ExchangeConnector.GroupDN 
  34:    WScript.Echo "IsUP: " & ExchangeConnector.IsUP 
  35:Next 
  ..: 
  ..: 
  ..:

The logic for using the ExchangeLink class is the same as that in Sample 2. In this case, Line 13 instantiates the ExchangeLink class. Instances of this class represent the links (objects under the queues container in ESM) that exist on the specified cComputerName (line 11) only. This WMI class does not work with the routing table and therefore does not offer an organization-wide view of the links.

Sample 3 Using the ExchangeLink WMI class 

   1:' VBScript script listing all ExchangeLink names and properties      
   2:' available with the WMI Exchange 2000 provider.                   
   .: 
   9:Option Explicit 
  10: 
  11:Const cComputerName = "LocalHost" 
  12:Const cWMINameSpace = "/root/cimv2/applications/exchange" 
  13:Const cWMIInstance  = "ExchangeLink" 
  ..:   
  ..:   
  24:Set ExchangeLinkList = _ 
                   GetObject("winmgmts:{impersonationLevel=impersonate}!//"& 
  25:                        cComputerName & "/" & _ 
  26:                        cWMINameSpace).InstancesOf(cWMIInstance) 
  27: 
  28:For each ExchangeLink in ExchangeLinkList 
  29:    WScript.Echo "---------------------------------------------" 
  30:    WScript.Echo "LinkName: " & ExchangeLink.LinkName 
  31:    WScript.Echo "  ProtocolName: " & ExchangeLink.ProtocolName 
  32:    WScript.Echo "  VirtualServerName: " & ExchangeLink.VirtualServerName 
  33:    WScript.Echo "  VirtualMachine: " & ExchangeLink.VirtualMachine  
  34:    WScript.Echo "  Version: " & ExchangeLink.Version 
  35:    WScript.Echo "  NumberOfMessages: " & ExchangeLink.NumberOfMessages 
  36:    WScript.Echo "  NextScheduledConnection: " & _ 
                                          ExchangeLink.NextScheduledConnection 
  37:    WScript.Echo "  OldestMessage: " & ExchangeLink.OldestMessage 
  38:    WScript.Echo "  SizeOfQueue: " & ExchangeLink.SizeOfQueue 
  39:    WScript.Echo "  LinkDN: " & ExchangeLink.LinkDN 
  40:    WScript.Echo "  ExtendedStateInfo: " & ExchangeLink.ExtendedStateInfo 
  41:    WScript.Echo "  IncreasingTime: " & ExchangeLink.IncreasingTime 
  42:    WScript.Echo "  StateFlags: 0x" & Hex (ExchangeLink.StateFlags) 
  43:    WScript.Echo "  StateActive: " & ExchangeLink.StateActive 
  44:    WScript.Echo "  StateReady: " & ExchangeLink.StateReady 
  45:    WScript.Echo "  StateRetry: " & ExchangeLink.StateRetry 
  46:    WScript.Echo "  StateScheduled: " & ExchangeLink.StateScheduled 
  47:    WScript.Echo "  StateRemote: " & ExchangeLink.StateRemote 
  48:    WScript.Echo "  StateFrozen: " & ExchangeLink.StateFrozen 
  49:    WScript.Echo "  TypeRemoteDelivery: " & _ 
                                             ExchangeLink.TypeRemoteDelivery 
  50:    WScript.Echo "  TypeLocalDelivery: " & ExchangeLink.TypeLocalDelivery 
  51:    WScript.Echo "  TypePendingRouting: " & _ 
                                             ExchangeLink.TypePendingRouting 
  52:    WScript.Echo "  TypePendingCategorization: " & _ 
  53:                                   ExchangeLink.TypePendingCategorization 
  54:    WScript.Echo "  TypeCurrentlyUnreachable: " & _ 
                                        ExchangeLink.TypeCurrentlyUnreachable 
  55:    WScript.Echo "  TypeDeferredDelivery: " & _ 
                                        ExchangeLink.TypeDeferredDelivery 
  56:    WScript.Echo "  TypeInternal: " & ExchangeLink.TypeInternal 
  57:    WScript.Echo "  SupportedLinkActions: " & _ 
                                         ExchangeLink.SupportedLinkActions 
  58:    WScript.Echo "  ActionKick: " & ExchangeLink.ActionKick 
  59:    WScript.Echo "  ActionFreeze: " & ExchangeLink.ActionFreeze 
  60:    WScript.Echo "  ActionThaw: " & ExchangeLink.ActionThaw 
  61:Next 
  ..:   
  ..:   
  ..:  
Using ExchangeQueue from WSH

The logic here is the same as in the previous samples. In Sample 4, the class to be instantiated at line 13 is the ExchangeQueue. Note that this script only retrieves the instances of the queues on the specified cComputerName (line 11). Like ExchangeLink, the ExchangeQueue class does not work with the routing table.

Sample 4 Using the ExchangeQueue WMI class 

   1:' VBScript script listing all ExchangeQueue names and properties    
   2:' available with the WMI Exchange 2000 provider.                      
   .: 
   9:Option Explicit 
  10: 
  11:Const cComputerName = "LocalHost" 
  12:Const cWMINameSpace = "root/cimv2/applications/exchange" 
  13:Const cWMIInstance  = "ExchangeQueue" 
  ..: 
  ..: 
  24:Set ExchangeServerQueueList= _ 
                    GetObject("winmgmts:{impersonationLevel=impersonate}!//"& 
  25:                         cComputerName & "/" & _ 
  26:                         cWMINameSpace).InstancesOf(cWMIInstance) 
  27: 
  28:For each ExchangeServerQueue in ExchangeServerQueueList 
  29:    WScript.Echo "---------------------------------------------" 
  30:    WScript.Echo "QueueName: " & ExchangeServerQueue.QueueName 
  31:    WScript.Echo "  ProtocolName: " & ExchangeServerQueue.ProtocolName 
  32:    WScript.Echo "  VirtualServerName: " & _ 
                                      ExchangeServerQueue.VirtualServerName 
  33:    WScript.Echo "  QueueName: " & ExchangeServerQueue.QueueName 
  34:    WScript.Echo "  VirtualMachine: " & _ 
                                      ExchangeServerQueue.VirtualMachine  
  35:    WScript.Echo "  Version: " & ExchangeServerQueue.Version 
  36:    WScript.Echo "  NumberOfMessages: " & _ 
                                      ExchangeServerQueue.NumberOfMessages 
  37:    WScript.Echo "  SizeOfQueue: " & ExchangeServerQueue.SizeOfQueue 
  38:    WScript.Echo "  IncreasingTime: " & _ 
                                      ExchangeServerQueue.IncreasingTime 
  39:    WScript.Echo "  MsgEnumFlagsSupported: 0x" & _ 
  40:                    Hex (ExchangeServerQueue.MsgEnumFlagsSupported) 
  41:    WScript.Echo "  GlobalStop: " & ExchangeServerQueue.GlobalStop  
  42:    WScript.Echo "  CanEnumFirstNMessages: " & _ 
  43:                            ExchangeServerQueue.CanEnumFirstNMessages 
  44:    WScript.Echo "  CanEnumSender: " & ExchangeServerQueue.CanEnumSender 
  45:    WScript.Echo "  CanEnumRecipient: " & _ 
                                    ExchangeServerQueue.CanEnumRecipient 
  46:    WScript.Echo "  CanEnumLargerThan: " & _ 
                                    ExchangeServerQueue.CanEnumLargerThan 
  47:    WScript.Echo "  CanEnumLargerThan: " & _ 
                                    ExchangeServerQueue.CanEnumOlderThan 
  48:    WScript.Echo "  CanEnumFrozen: " & ExchangeServerQueue.CanEnumFrozen 
  49:    WScript.Echo "  CanEnumNLargestMessages: " & _ 
  50:                            ExchangeServerQueue.CanEnumNLargestMessages 
  51:    WScript.Echo "  CanEnumNOldestMessages: " & _ 
  52:                            ExchangeServerQueue.CanEnumNOldestMessages 
  53:    WScript.Echo "  CanEnumFailed: " & ExchangeServerQueue.CanEnumFailed 
  54:    WScript.Echo "  CanEnumAll: " & ExchangeServerQueue.CanEnumAll 
  55:    WScript.Echo "  CanEnumInvertSense: " & _ 
                                       ExchangeServerQueue.CanEnumInvertSense 
  56:Next 
  ..: 
  ..: 
  ..:
Using ExchangeClusterResource from WSH

The ExchangeClusterResource class retrieves the status of a clustered Exchange resource such as a virtual server name. Except for this point and the list of properties available from this class, the script logic is exactly the same as the previous samples. Like ExchangeLink and ExchangeQueue, this WMI class does not work with the routing table and therefore does not offer an organization-wide view of the clustered resources.

Sample 5 Using the ExchangeClusterResource WMI class 

   1:' VBScript script listing all ExchangeClusterResource names and properties  
   2:' available with the WMI Exchange 2000 provider.        
   .: 
   9:Option Explicit 
  10: 
  11:Const cComputerName = "LocalHost" 
  12:Const cWMINameSpace = "/root/cimv2/applications/exchange" 
  13:Const cWMIInstance  = "ExchangeClusterResource" 
  ..: 
  ..: 
  24:Set ExchangeServerClusterList = _ 
  25:             GetObject("winmgmts:{impersonationLevel=impersonate}!//" & _ 
  26:                       cComputerName & "/" & _ 
  27:                       cWMINameSpace).InstancesOf(cWMIInstance) 
  28: 
  29:For each ExchangeServerCluster in ExchangeServerClusterList 
  30:    WScript.Echo "---------------------------------------------" 
  31:    WScript.Echo "VirtualMachine:" & ExchangeServerCluster.VirtualMachine 
  32:    WScript.Echo "Name: " & ExchangeServerCluster.Name 
  33:    WScript.Echo "Type: " & ExchangeServerCluster.Type 
  34:    WScript.Echo "State: " & ExchangeServerCluster.State 
  35:Next 
  ..: 
  ..: 
  ..:

A Script to Explore the Exchange 2000 Logical View

Samples 1 through 5 show how to access the Exchange 2000 WMI classes. Sample 1 can be used as a base to build a more complex script that retrieves the complete set of information shown in Figure 3.

The EnumE2KinXL.wsf script uses Exchange 2000 COM components to load the information into a Microsoft® Excel spreadsheet. This script acts as a "browser" or an "explorer" for Exchange 2000.

Each COM component provides its own set of properties and methods to manage Exchange 2000. This script illustrates how to combine the technologies and how to reuse a set of information retrieved from one COM object type with another COM object type.

The EnumE2KinXL.wsf script is the basis of all subsequent scripts because it provides a detailed example of how to use scripting for Exchange 2000 management. It shows how to access a specific Exchange 2000 component, what features the component offers, and how to complete each step as shown in Figure 3. Each step has a corresponding sample (Sample 7 through Sample 24).

Sample 6 Loading the object hierarchy 

   1:<!-- VBScript script loading the complete Exchange 2000 object         --> 
   2:<!-- hierarchy into an Excel sheet using WMI, CDOEXM, ADSI,            --> 
   3:<!-- CDO, and ADO (with values of different properties).               -->                        
  ..: 
  10:<job> 
  11:        <script language="VBScript" src="GetMSExchangeOrgFunction.vbs" /> 
  12: 
  13:        ' Here you can use CDOEX or ADSI to enumerate mailbox properties. 
  14:        <script language="VBScript" _ 
  15:              src="EnumMailboxStoreFunction (CDOEX).vbs" /> 
  16:<!--    <script language="VBScript" _ 
  17:              src="EnumMailboxStoreFunction (ADSI).vbs" /> --> 
  18: 
  19:        <script language="VBScript" _ 
  20:              src="BrowseStoreFolderFunction (ADO).vbs" /> 
  21:        <script language="VBScript" _ 
  22:              src="ADSearchFunction1.vbs" /> 
  23:        <script language="VBScript" _ 
  24:              src="EnumMessageContentFunction.vbs" /> 
  25:        <script language="VBScript" _ 
  26:              src="EnumFieldsCollectionFunction.vbs" /> 
  27: 
  28:        <script language="VBScript"> 
  29: 
  30:        Option Explicit 
  31: 
  32:        Const cEnumMaiboxTree           = True 
  33:        Const cEnumFolderTree           = True 
  34:        Const cEnumFieldsCollection     = False 
  35:        Const cEnumMessageContent       = False 
  36:         
  37: 
  38:        Const cComputerName             = "LocalHost" 
  39:        Const cWMINameSpace          = "/root/cimv2/applications/exchange" 
  40:        Const cWMIInstance              = "ExchangeServerState" 
  ..: 
  ..: 
  69:        ' ---------------------------------------------------------------- 
  70:        ' This script must be run locally to the Exchange server. 
  71:        Set WNetwork = Wscript.CreateObject("Wscript.Network") 
  72:        strCurrentComputerName = WNetwork.ComputerName 
  73:        strCurrentUserName = WNetwork.UserName 
  74:        Wscript.DisconnectObject (WNetwork) 
  75:        Set WNetwork = Nothing 
  76: 
  77:        ' ---------------------------------------------------------------- 
  78:        ' Getting the current default domain (DN and FQDN). 
  79: 
  80:        Set objRoot = GetObject("LDAP://RootDSE") 
  81:        strDefaultDomainNC = objRoot.Get("DefaultNamingContext") 
  82:        Set objRoot = Nothing 
  83: 
  84:        ' Bind to the root domain to get its canonical name for UPN.  
  85:        Set objDefaultDomainNC = GetObject("LDAP://" & strDefaultDomainNC) 
  86: 
  87:        ' Retrieve a constructed property.  
  88:        ' First do a GetInfoEx (for UPN construction). 
  89:        objDefaultDomainNC.GetInfoEx Array("canonicalName"), 0 
  90:        strCanonicalNameDefaultDomain = _ 
                                   objDefaultDomainNC.Get("canonicalName") 
  91: 
  92:        Set objDefaultDomainNC = Nothing 
  ..: 
  ..: 
  97:        ' Bind to an Excel worksheet object. 
  98:        Set objXL = WScript.CreateObject("EXCEL.application") 
  99: 
 100:        ' Make it visible. 
 101:        objXL.Visible = True 
 102: 
 103:        ' Open Excel and start an empty workbook. 
 104:        objXL.workbooks.Add 
 105: 
 106:        ' Put the pointer on the A1 cell. 
 107:        objXL.ActiveSheet.range("A1").Activate 
 ...: 
 ...: 
 112:        ' ---------------------------------------------------------------- 
 113:        ' Start at the higher object level, the Exchange server itself. 
 114: 
 115:        Set objWMIExchangeServers = _ 
 116:             GetObject("winmgmts:{impersonationLevel=impersonate}!//" & _ 
 117:                       cComputerName & _ 
 118:                       cWMINameSpace).InstancesOf(cWMIInstance) 
 119: 
 120:        EnumExchangeServers objWMIExchangeServers 
 121: 
 122:        ' Close the workbook. This will prompt the user to choose  
 123:        ' where to save the XLS. 
 124:        objXL.workbooks.close 
 125:        objXL.Quit 
 126:        WScript.DisconnectObject objXL 
 127:        Set objXL = Nothing 
 128: 
 129:        WScript.Quit (0) 
 ...: 
 ...: 
 ...:

Sample 6 must be run on an Exchange 2000 server with Excel 2000 installed. The script uses Excel to review and display the collected data. For the sake of simplicity, you should run the script in an Exchange 2000 test environment (this can be a single server). In a real business situation, the script would retrieve too much data.

The script begins (lines 11 to 26) with various functions. These functions are explained in the following pages of this document. Each has a specific purpose related to the COM technology that is used.

Note By default, the script uses CDOEX to access user information. As explained in the "How Is CDOEXM Related to ASDI and CDOEX?" section on page 7, ADSI can be used instead of CDOEX. This is why lines 16 and 17 are commented out. These lines show how to use ADSI to retrieve user information.

Lines 32 to 35 define some constants to limit the amount of information. Each of these contains a Boolean value.

  • cEnumMaiboxTree: When True, the script will explore the mailbox folder hierarchy. To minimize the amount of data collected, and for security reasons, the only mailbox accessed is the mailbox of the user who is currently logged on.

  • cEnumFolderTree: When True, the script will explore the entire public folder hierarchy. Set this constant to True with caution; if you have a large number of public folders it can take a lot of resources. Again, it's strongly recommended that you run Sample 6 in a test environment.

  • cEnumFieldsCollection: When True, the script will enumerate the CDO Fields collection associated with the CDOEX object. This constant is used in every function retrieving an ADO Fields collection from an object.

  • cEnumMessageContent: When cEnumMaiboxTree or cEnumFolderTree is True and a folder contains messages, cEnumMessageContent examines the message properties (header, body parts, any attachments, and so on).

Lines 38 to 40 of Sample 6 define constants from Sample 1.

In lines 69 to 92, the script retrieves information about the domain name and the computer name.

Lines 97 to 107 initialize the Excel 2000 COM objects to load the retrieved data into a new Excel spreadsheet.

After the basic steps are complete, the script explores the Exchange 2000 server using the different COM technologies.

In lines 115 to 118, the script instantiates the ExchangeServerState WMI class. The returned result is a collection of Exchange 2000 servers in the organization. This collection is passed to the EnumExchangeServers function (line 120).

This is where the Exchange 2000 COM exploration with scripts really begins.

Getting Information About the Exchange 2000 Server with CDOEXM

The following sections explain how to retrieve information about servers, storage groups, and mailbox stores using CDOEXM.

Retrieving More Exchange 2000 Server Information With CDOEXM

This section refers to points 1, 2, A, and B in Figure 3. 

In lines 140 to 177 of Sample 7 below, the EnumExchangeServers function uses the same Windows Management Instrumentation (WMI) information retrieved at line 120 in Sample 6. What's interesting here is the way the information is displayed. The DisplayText function loads the information into an Excel spreadsheet (lines 140 to 177). Note that CDO for Exchange Management (CDOEXM) is invoked at lines 137 and 181–209.

Sample 7 Getting more Exchange 2000 server information with CDOEXM 

 ...:  ...: 
 132:Private Function EnumExchangeServers (objWMIExchangeServers) 
 ...: 
 ...: 
 137:    Set objCDOEXMExchangeServer = CreateObject("CDOEXM.ExchangeServer") 
 138: 
 139:    For each objWMIExchangeServer in objWMIExchangeServers 
 140:        DisplayText "Name(WMI)", objWMIExchangeServer.Name 
 ...: 
 ...: 
 176:        DisplayText "ServicesState(WMI)", _ 
 177:                     objWMIExchangeServer.ServicesState 
 178: 
 179:        ' Switch to CDOEXM to get CDOEXM information  
 180:        ' by using the WMI server name. 
 181:        objCDOEXMExchangeServer.DataSource.Open(objWMIExchangeServer.Name) 
 ...: 
 ...: 
 188:        DisplayText "ExchangeVersion(CDOEXM)", _ 
 189:                    objCDOEXMExchangeServer.ExchangeVersion 
 190:        DisplayText "SubjectLoggingEnabled(CDOEXM)", _ 
 191:                    objCDOEXMExchangeServer.SubjectLoggingEnabled 
 192:        DisplayText "MessageTrackingEnabled(CDOEXM)", _ 
 193:                    objCDOEXMExchangeServer.MessageTrackingEnabled 
 194:        DisplayText "DaysBeforeLogFileRemoval(CDOEXM)", _ 
 195:                    objCDOEXMExchangeServer.DaysBeforeLogFileRemoval 
 196:        DisplayText "Name(CDOEXM)", objCDOEXMExchangeServer.Name 
 197:        DisplayText "ServerType(CDOEXM)", _ 
 198:                    objCDOEXMExchangeServer.ServerType 
 199:        DisplayText "DirectoryServer(CDOEXM)", _ 
 200:                    objCDOEXMExchangeServer.DirectoryServer 
 201: 
 202:        If cEnumFieldsCollection Then _ 
 203:           EnumFieldsCollection objCDOEXMExchangeServer.Fields 
 204: 
 205:           ' Enumerate the Storage Group of the current server 
 206:           ' (where the script is running). 
 207:           If strCurrentComputerName = objCDOEXMExchangeServer.Name Then 
 208:              EnumStorageGroups objCDOEXMExchangeServer 
 209:           End If 
 ...: 
 ...: 
 213:    Next 
 ...: 
 ...: 
 218:End Function

CDOEXM comes with a set of new objects and interfaces related to the Exchange 2000 server. The object used in Sample 7 is the CDOEXM.ExchangeServer object instantiated at line 137. With this object, the script can retrieve CDOEXM information by opening a data source with the Exchange server name retrieved from WMI (line 181).

Table 1 The CDOEXM object for the Exchange 2000 server

Name (ro)

The name of the Exchange server (line 196).

ExchangeVersion (ro)

The version of the Exchange server (lines 188 and 189).

StorageGroups (ro)

The list of URLs to the storage groups on this server. This parameter will be used in the EnumStorageGroups function called on line 208.

SubjectLoggingEnabled (rw)

Indicates whether subject logging and display is enabled for this server (lines 190 and 191).

MessageTrackingEnabled (rw)

Indicates whether message tracking is enabled for this server (lines 192 and 193).

DaysBeforeLogFileRemoval (rw)

The number of days log files are retained on the server (lines 194 and 195).

ServerType (rw)

Indicates whether this server is a front-end only server relaying commands to a back-end server for execution (lines 197 and 198).
0=FrontEnd, 1=BackEnd

DirectoryServer (ro)

The domain controller used by services on this computer (lines 199 and 200).

GetInterface (ro)

Returns the specified interface on the object.

DataSource (ro)

Returns the IDataSource interface on the object (line 181).

Fields (ro)

Returns the Fields collection for the object (lines 202 and 203).

Lines 202 and 203 retrieve the ActiveX Data Objects (ADO) Fields collection associated with the Exchange 2000 server. See Sample 24 for the EnumFieldsCollection function.

Management Point 1 Retrieving server information from WMI and CDOEXM 

At this stage of the script:

  • You have a complete list of the Exchange 2000 servers in the organization, with some details (version, server type, and so on).

  • You know the status of each Exchange 2000 server in the organization.

  • You can read and configure logging and message tracking for each Exchange server in the organization.

  • You know whether each server is a front-end (FE) or back-end (BE) server.

  • You have a pointer to get the configuration of the storage groups for each Exchange server.

See points 1, 2, A, and B in Figure 3 to locate this WMI and CDOEXM operation in the Exchange 2000 logical view.

Retrieving Exchange 2000 Storage Group Information with CDOEXM

This section refers to points 3 and C in Figure 3. 

Line 208 of Sample 7 calls the EnumStorageGroups function with the CDOEXM.ExchangeServer object as parameter. This function (see Sample 8) retrieves the list of storage groups configured on this server.

Note the condition at lines 207–209 in Sample 7. The "If...Then" structure restricts the exploration to the local Exchange server because the Exchange OLE DB provider (used later in Sample 21) does not allow any remote connection. This also helps minimize the data collected.

Sample 8 uses the CDOEXM.StorageGroup object, which is instantiated at line 226. Line 228 defines a loop through the storage groups in the collection. Inside the loop, a data source is opened (line 232) based on the distinguished name retrieved from the collection. The script extracts the values of properties in each group (lines 239 to 245).

Sample 8 Retrieving storage group information with CDOEXM 

 221:Private Function EnumStorageGroups (objCDOEXMExchangeServer) 
 ...: 
 ...: 
 226:     Set objStorageGroup = CreateObject("CDOEXM.StorageGroup") 
 227: 
 228:     For Each urlStorageGroup In objCDOEXMExchangeServer.StorageGroups 
 ...: 
 232:         objStorageGroup.DataSource.Open (urlStorageGroup) 
 ...: 
 ...: 
 239:         DisplayText "Storage Group Name(CDOEXM)", objStorageGroup.Name 
 240:         DisplayText "LogFilePath(CDOEXM)", objStorageGroup.LogFilePath 
 241:         DisplayText "SystemFilePath(CDOEXM)", _ 
 242:                     objStorageGroup.SystemFilePath 
 243:         DisplayText "CircularLogging(CDOEXM)", _ 
 244:                     objStorageGroup.CircularLogging 
 245:         DisplayText "ZeroDatabase(CDOEXM)", objStorageGroup.ZeroDatabase 
 246: 
 247:         ' At this level of the object hierarchy, you can move the log  
 248:         ' and system files of the storage group to another location. 
 249:         ' objStorageGroup.MoveLogFiles <New File System Path> 
 250:         ' objStorageGroup.MoveSystemFiles <New File System Path> 
 251: 
 252:         If cEnumFieldsCollection Then _ 
 253:            EnumFieldsCollection objStorageGroup.Fields 
 254: 
 255:            EnumMailboxStoreDBs objStorageGroup 
 256: 
 257:            EnumPublicStoreDBs objStorageGroup 
 ...: 
 ...: 
 261:     Next 
 ...: 
 ...: 
 266:End Function

Note Lines 249 and 250 of Sample 8 are commented out. They provide information only. The script does not move logs and system files when executed.

Table 2 The CDOEXM.StorageGroup object

Name (rw)

The name of the object (line 239).

PublicStoreDBs (ro)

The list of URLs to the public store databases on this server. This parameter is used in the EnumPublicStoreDBs function at line 257.

MailboxStoreDBs (ro)

The list of URLs to the mailbox store databases on this server. This parameter is used in the EnumMailboxStoreDBs function at line 255.

LogFilePath (ro)

The transaction log location (line 240).

SystemFilePath (ro)

The system path location (lines 241 and 242).

CircularLogging (rw)

Indicates whether circular logging is enabled (lines 243 and 244).

ZeroDatabase (rw)

Indicates whether deleted database pages should be zeroed out (lines 245).

MoveLogFiles

Changes the log file path. LogFilePath is a directory in the file system. For new objects, this will set the initial value of the LogFilePath property. For an existing object, the path can only be changed if running on the local machine (line 249).

MoveSystemFiles

Changes the system file path. SystemFilePath is a directory in the file system. For new objects, this will set the initial value of the SystemFilePath property. For an existing object, the path can only be changed if running on the local machine (line 250).

GetInterface

Returns the specified interface on the object.

DataSource (ro)

Returns the IDataSource interface on the object (line 232).

Fields (ro)

Returns the Fields collection for the object (lines 252 and 253).

Lines 252 and 253 retrieve the ADO Fields collection associated with an Exchange 2000 server. See Sample 24 for the EnumFieldsCollection function.

Management Point 2 Retrieving storage group information with CDOEXM 

At this stage of the script:

  • You know the storage group configuration. That is, you know the locations of the log and system files, and you can move them.

  • You can enable and disable circular logging in each storage group.

  • You can enable and disable the zeroDatabase feature.

  • You have a pointer to get the configuration of all mailboxes and public stores in the storage group.

See points 3 and C in Figure 3 to locate this CDOEXM operation in the Exchange 2000 logical view.

Retrieving Exchange 2000 Mailbox Store Information with CDOEXM

This section refers to points 4 and D in Figure 3. 

Line 255 of Sample 8 calls the EnumMailboxStoreDBs function with the CDOEXM.StorageGroup object as a parameter. The EnumMailboxStoreDBs function retrieves the list of mailbox stores in the storage group.

In Sample 9, the list of mailbox stores is retrieved from a collection (line 276). Inside the "For Each" loop, a data source is opened (line 280) based on the distinguished name retrieved from the collection. The script extracts each mailbox store's property values (lines 287 to 303).

Sample 9 Retrieving mailbox store information with CDOEXM 

 269:Private Function EnumMailboxStoreDBs (objStorageGroup) 
 ...: 
 ...: 
 274:        Set objMailboxStoreDB = CreateObject("CDOEXM.MailBoxStoreDB") 
 275: 
 276:        For Each urlMailboxStoreDB In objStorageGroup.MailboxStoreDBs 
 ...: 
 ...: 
 280:            objMailboxStoreDB.DataSource.Open (urlMailboxStoreDB) 
 ...: 
 ...: 
 287:            DisplayText "Name(CDOEXM)", objMailboxStoreDB.Name 
 288:            DisplayText "DaysBeforeGarbageCollection", _ 
 289:                         objMailboxStoreDB.DaysBeforeGarbageCollection 
 290:            DisplayText "DaysBeforeDeletedMailboxCleanup", _ 
 291:                         objMailboxStoreDB.DaysBeforeDeletedMailboxCleanup 
 292:            DisplayText "GarbageCollectOnlyAfterBackup", _ 
 293:                         objMailboxStoreDB.GarbageCollectOnlyAfterBackup 
 294:            DisplayText "DBPath", objMailboxStoreDB.DBPath 
 295:            DisplayText "SLVPath", objMailboxStoreDB.SLVPath 
 296:            DisplayText "PublicStoreDB", objMailboxStoreDB.PublicStoreDB 
 297:            DisplayText "OfflineAddressList", _ 
 298:                         objMailboxStoreDB.OfflineAddressList 
 299:            DisplayText "Status", objMailboxStoreDB.Status 
 300:            DisplayText "Enabled", objMailboxStoreDB.Enabled 
 301:            DisplayText "StoreQuota", objMailboxStoreDB.StoreQuota 
 302:            DisplayText "OverQuotaLimit", objMailboxStoreDB.OverQuotaLimit 
 303:            DisplayText "HardLimit", objMailboxStoreDB.HardLimit 
 304: 
 305:            ' At this level of the object hierarchy, 
 306:            ' you can mount/dismount the mailbox store. 
 307:            ' objMailboxStoreDB.Dismount 
 308:            ' objMailboxStoreDB.Mount 
 309: 
 310:            ' At this level of the object hierarchy, 
 311:            ' you can move the mailbox store 
 312:            ' to another location. 
 313:            ' objMailboxStoreDB.MoveDataFiles <New Data File Path> 
 314: 
 315:            If cEnumFieldsCollection Then _ 
 316:               EnumFieldsCollection objMailboxStoreDB.Fields 
 317: 
 318:            EnumMailboxStore urlMailboxStoreDB 
 ...: 
 ...: 
 322:        Next 
 ...: 
 ...: 
 327:End Function

Note Lines 307, 308, and 313 are commented out. They provide information only. The script does not mount, dismount, or move database files when executed.

Table 3 The CDOEXM object for the mailbox store

Name (rw)

The name of the object (line 287).

PublicStoreDB (rw)

The default public store for mailboxes on this database (line 296).

OfflineAddressList (rw)

The offline address list for mailboxes on this database (lines 297 and 298).

DBPath (ro)

The path to the Exchange database file (line 294).

SLVPath (ro)

The path to the Exchange streaming database file (line 295).

Status (ro)

The current online status of the database (line 299).

Enabled (rw)

Indicates whether this store should be mounted at start-up (line 300).

StoreQuota (rw)

The quota limit at which mailboxes receive warning messages (line 301).

OverQuotaLimit (rw)

The quota limit at which sending messages is prohibited (line 302).

HardLimit (rw)

The quota limit at which incoming messages are rejected (line 303).

DaysBeforeGarbageCollection (rw)

The number of days to keep deleted items (line 289).

DaysBeforeDeletedMailboxCleanup (rw)

The number of days deleted mailboxes are retained in the database (lines 290 and 291).

GarbageCollectOnlyAfterBackup (rw)

Indicates whether items should not be permanently deleted until the store has been backed up (lines 292 and 293).

MoveDataFiles

Changes the database file paths. The DBPath and SLVPath parameters are file paths on the server. The Flags parameter is ignored. For an existing object, the path can only be changed if running on the local machine (line 313).

Mount

Mounts the store for use. The Time-out parameter is ignored (line 308).

Dismount

Dismounts the store. The Time-out parameter is ignored (line 307).

GetInterface

Returns the specified interface on the object.

DataSource (ro)

Returns the IDataSource interface on the object (line 280).

Fields (ro)

Returns the Fields collection for the object (lines 315 and 316).

Lines 315 and 316 retrieve the ADO Fields collection associated with the Exchange 2000 server. See Sample 24 for the EnumFieldsCollection function.

Management Point 3 Retrieving mailbox store information with CDOEXM 

At this stage of the script:

  • You know the database and stream file locations of each mailbox store in the storage group.

  • You know the distinguished name of the default public store for the mailbox store.

  • You have the offline address list for the mailbox store.

  • You know whether the mailbox store is mounted or dismounted, and its default state at the Exchange startup.

  • You have a set of information about mailbox store quotas.

  • You have a set of information about the mailbox store cleanup process.

  • You can mount and dismount the mailbox store and move its data files to another location.

See points 4 and D in Figure 3 to locate this operation in the Exchange 2000 logical view.

Getting Information About Mailboxes

The previous sections explored the Exchange 2000 server, its storage groups, and stores using CDOEXM. The next sections explain how to get detailed information about mailboxes.

Retrieving Mailboxes with ADSI

This section refers to points 5, 6, E, and F in Figure 3. 

Line 318 of Sample 9 calls the EnumMailboxStore function with the distinguished name of the mailbox store as a parameter. The first part of the EnumMailboxStore function (see Sample 10) retrieves the list of the mailboxes in the mailbox store (lines 31 to 35).

The list of mailboxes is retrieved from a query based on ADO and performed by the ADSearch function. For this query, ADO uses the ADSI OLE DB provider (see Figure 2).

The ADSI OLE DB provider uses the IDirectorySearch interface and an LDAP query to get the list of mailboxes. For the purposes of this white paper, the most significant detail is that the search operation is executed by ADSI based on an ADO query. This is why steps 5 and E in Figure 3 include ADO.

Note This document does not focus on ADSI. See the Microsoft MSDN Web site or the Compaq Active Answers Web site at https://activeanswers.compaq.com/ for more information about ADSI search operations. The ADSearch function is discussed in more detail in "Querying Active Directory with ADSI and ADO" on page 111 of the Appendix.

Note Addresses of third-party Web sites referenced in this white paper are provided to help you find the information you need. This information is subject to change without notice. Microsoft in no way guarantees the accuracy of this third-party information.

The query selection is based on the homeMDB attribute initialized for any mailbox-enabled object in Microsoft® Active Directory®. The homeMDB attribute (line 32) is simply the distinguished name of the mailbox store housing the mailbox.

The search retrieves a collection of ADsPath objects stored in a Dictionary object (line 31). The Dictionary object is part of the Scripting Run-Time Library. The Dictionary object stores heterogeneous data that can be accessed using an ordinal position or a string used as key. For more information, see the MSDN Library at https://msdn.microsoft.com/library.

The ADSearch function can retrieve other attributes besides the homeMDB attribute. It is also used in the "Moving Exchange 2000 Mailboxes Based on an LDAP Query" script in the second part of this document.

Note The mailbox store object in Active Directory contains the homeMDBBL* *attribute. This multi-valued attribute contains a list of the distinguished name attributes of users with mailboxes in the mailbox store. The LDAP search operation is more efficient than homeMDBBL because the distinguishedName property is rarely the only information you want. If the homeMDBBL property is used, you have to make a bind operation with each distinguished name to get more information about the object itself.

Management Point 4 Retrieving mailboxes from a mailbox store with ADO/ADSI 

At this stage of the script:

  • You know the distinguished name of a mailbox store.

  • You have a distinguished name list of the mailbox-enabled Active Directory users in the mailbox store.

See points 5, 6, E, and F in Figure 3 to locate this operation in the Exchange 2000 logical view.

Retrieving Mailbox-Enabled Object Properties with ADSI

This section refers to point 7 in Figure 3. 

Line 54 of Sample 10 begins enumerating the distinguished names retrieved by the ADSearch function. For each loop, the script binds to an Active Directory mailbox-enabled object (lines 56 and 57). It retrieves the list of properties associated with the mailbox-enabled object (lines 60 to 177). Note that at lines 60 and 61 the script obtains the mailNickName and the displayName property values and stores them in variables for later use.

Note In this case, it is mandatory to make a bind operation because the script uses the IMailboxStore aggregated interface (see Sample 11, lines 200 to 212) to retrieve the mailbox properties associated with the ADSI.User object. If the objective is not to retrieve the mailbox properties, the bind operation is not required and the LDAP search operation can retrieve the ADSI.User properties. This makes the code more efficient. See the prior note about the homeMDBBL property.

To make the script more readable, some parts of the code that retrieves the ADSI.User properties have been removed from Sample 10.

Accessing Active Directory from ADSI means using the LDAP namespace. Some ADSI.User object properties are not supported in the LDAP namespace (lines 80, 101, 104, 111, 132, 145, 150, and 163). These properties are related to user logon management and are part of the Group Policy Object (GPO) in Windows 2000. These properties are supported in Microsoft® Windows NT® 4.0 with the ADSI WinNT namespace. For more information about ADSI, see the Microsoft Platform SDK at https://msdn.microsoft.com/library .

Sample 10 Retrieving a mailbox and its associated mailbox-enabled object properties from ADSI 

    1:' VBScript function to enumerate the mailboxes in a mailbox store from    
   2:' an ADSI user object class.                                              
   .: 
   9:Option Explicit 
  10: 
  11:' ------------------------------------------------------------------------ 
  12:Function EnumMailboxStore (urlMailboxStoreDB) 
  ..: 
  ..: 
  26:    ' -------------------------------------------------------------------- 
  27:    WScript.Echo Space (intX + 1) & _ 
  28:            "Retrieving User/mailbox list via 'ADSI/ADO' LDAP Query." 
  29: 
  30:    ' Search Active Directory for the list of user mailboxes. 
  31:    Set objResultList = ADSearch ("LDAP://" & strDefaultDomainNC, _ 
  32:                                  "(homeMDB=" & urlMailboxStoreDB & ")", _ 
  33:                                  "ADsPath", _ 
  34:                                  "subTree", _ 
  35:                                  False) 
  36: 
  37:    ' -------------------------------------------------------------------- 
  38:    objResult = objResultList.Items 
  ..: 
  ..: 
  54:    For intIndice = 1 to (objResultList.Count - 1) 
  55: 
  56:        Set objUser = GetObject (objResult (intIndice)) 
  57:        objUser.GetInfo 
  58: 
  59:        ' Getting two properties for display facilities. 
  60:        strAlias = objUser.Get ("mailNickName") 
  61:        strDisplayName = objUser.Get ("displayName") 
  ..: 
  ..: 
  76:        strTemp = "" : strTemp = objUser.AccountDisabled 
  77:        DisplayText "AccountDisabled(ADSI)",        strTemp 
  78:        strTemp = "" : strTemp = objUser.AccountExpirationDate 
  79:        DisplayText "AccountExpirationDate(ADSI)",  strTemp 
  80:        ' objUser.BadLoginAddress 
  81:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
  82:        DisplayText "BadLoginAddress(ADSI)",        strTemp 
  83:        strTemp = "" : strTemp = objUser.BadLoginCount 
  84:        DisplayText "BadLoginCount(ADSI)",          strTemp 
  85:        strTemp = "" : strTemp = objUser.Department 
  86:        DisplayText "Department(ADSI)",             strTemp 
  ..: 
  ..: 
  98:        DisplayText "FirstName(ADSI)",              strTemp 
  99:        strTemp = "" : strTemp = objUser.FullName 
 100:        DisplayText "FullName(ADSI)",               strTemp 
 101:        ' objUser.GraceLoginsAllowed 
 102:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 103:        DisplayText "GraceLoginsAllowed(ADSI)",     strTemp 
 104:        ' objUser.GraceLoginsRemaining 
 105:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 106:        DisplayText "GraceLoginsRemaining(ADSI)",   strTemp 
 107:        strTemp = "" : strTemp = objUser.HomeDirectory 
 108:        DisplayText "HomeDirectory(ADSI)",          strTemp 
 109:        strTemp = "" : strTemp = objUser.HomePage 
 110:        DisplayText "HomePage(ADSI)",               strTemp 
 111:        ' objUser.IsAccountLocked 
 112:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 113:        DisplayText "IsAccountLocked(ADSI)",        strTemp 
 114:        strTemp = "" : strTemp = objUser.Languages 
 115:        DisplayText "Languages(ADSI)",              strTemp 
 ...: 
 ...: 
 126:        strTemp = "" : strTemp = objUser.LoginScript 
 127:        DisplayText "LoginScript(ADSI)",            strTemp 
 128:        strTemp = "" : strTemp = objUser.LoginWorkstations 
 129:        DisplayText "LoginWorkstations(ADSI)",      strTemp 
 130:        strTemp = "" : strTemp = objUser.Manager 
 131:        DisplayText "Manager(ADSI)",                strTemp 
 132:        ' objUser.MaxLogins 
 133:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 134:        DisplayText "MaxLogins(ADSI)",              strTemp 
 ...: 
 ...: 
 143:        strTemp = "" : strTemp = objUser.OtherName 
 144:        DisplayText "OtherName(ADSI)",              strTemp 
 145:        ' objUser.PasswordExpirationDate 
 146:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 147:        DisplayText "PasswordExpirationDate(ADSI)", strTemp 
 148:        strTemp = "" : strTemp = objUser.PasswordLastChanged 
 149:        DisplayText "PasswordLastChanged(ADSI)",    strTemp 
 150:        ' objUser.PasswordMinimumLength 
 151:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 152:        DisplayText "PasswordMinimumLength(ADSI)",  strTemp 
 153:        strTemp = "" : strTemp = objUser.PasswordRequired 
 ...: 
 ...: 
 161:        strTemp = "" : strTemp = objUser.Profile 
 162:        DisplayText "Profile(ADSI)",                strTemp 
 163:        ' objUser.RequireUniquePassword 
 164:        strTemp = "" : strTemp = "(Not supported in LDAP namespace)" 
 165:        DisplayText "RequireUniquePassword(ADSI)",  strTemp 
 166:        strTemp = "" : strTemp = objUser.SeeAlso 
 167:        DisplayText "SeeAlso(ADSI)",                strTemp 
 168:        strTemp = "" : strTemp = objUser.TelephoneHome 
 ...: 
 ...: 
 174:        strTemp = "" : strTemp = objUser.TelephonePager 
 175:        DisplayText "TelephonePager(ADSI)",         strTemp 
 176:        strTemp = "" : strTemp = objUser.Title 
 177:        DisplayText "Title(ADSI)",                  strTemp 
 178: 
 179:        ' At this level of the object hierarchy, you can determine 
 180:        ' the group membership, and set and change the password 
 181:        ' of a user object. 
 182:        ' objUser.Groups  
 183:        ' objUser.SetPassword  
 184:        ' objUser.ChangePassword  
 ...: 
 ...:

The comments on lines 179 to 184 refer to information that can be retrieved about mailbox-enabled objects. Because a mailbox-enabled object is a user object class in Active Directory, you can determine and change which group it belongs to (line 182). In addition, passwords can be set (line 183) and changed (line 184) at this level of the object hierarchy. Note that these features are purely ADSI related.

After Active Directory mailbox-enabled object properties are retrieved, the script uses CDOEXM to retrieve their associated mailbox properties (see Sample 11, lines 200 to 224). At this point CDOEXM acts as an extension of ADSI by providing the IMailboxStore* interface to the ADSI.User object. The IMailboxStore *interface is an aggregated interface to the ADSI.User object class. For more information about the ADSI.User object class, see https://msdn.microsoft.com/library.

Management Point 5 Retrieving mailbox-enabled object properties with ADSI 

At this stage of the script:

  • You have the property list of the ADSI.User mailbox-enabled object.

  • You can get and change the ADSI.User group membership.

  • You can change or reset the ADSI.User password.

  • You have an interface to retrieve mailbox configuration information with the help of the IMailboxStore interface of CDOEXM.

See point 7 in Figure 3 to locate this operation in the Exchange 2000 logical view.

Retrieving Mailbox Properties with CDOEXM from ADSI

This section refers to points 8 and 9 in Figure 3. 

When the Exchange System Manager (ESM) is installed on a Windows 2000 computer, CDOEXM is registered under Windows 2000 and installed as an extension of Active Directory Service Interfaces (ADSI). A set of mailbox properties and methods from the mailbox-enabled ADSI object is retrieved through CDOEXM at lines 200 through 217 of Sample 11. The mailbox-enabled object is instantiated at line 56 in Sample 10.

Sample 11 Retrieving mailbox properties with CDOEXM from ADSI 

 ...: 
 196:        DisplayText "Data retrieved via _ 
                                        'IMailboxStore  CDOEXM' interface", "" 
 ...: 
 200:        DisplayText "DaysBeforeGarbageCollection(CDOEXM)", _ 
 201:                       objUser.DaysBeforeGarbageCollection 
 202:        DisplayText "EnableStoreDefaults(CDOEXM)", _ 
 203:                       objUser.EnableStoreDefaults 
 204:        DisplayText "GarbageCollectOnlyAfterBackup(CDOEXM)", _ 
 205:                       objUser.GarbageCollectOnlyAfterBackup 
 206:        DisplayText "Hardlimit(CDOEXM)", objUser.Hardlimit 
 207:        DisplayText "HomeMDB(CDOEXM)", objUser.HomeMDB 
 208:        DisplayText "OverQuotaLimit(CDOEXM)", objUser.OverQuotaLimit 
 209:        DisplayText "OverrideStoreGarbageCollection(CDOEXM)", _ 
 210:                       objUser.OverrideStoreGarbageCollection 
 211:        DisplayText "RecipientLimit(CDOEXM)", objUser.RecipientLimit 
 212:        DisplayText "StoreQuota(CDOEXM)", objUser.StoreQuota 
 ...: 
 215:        For Each strDelegate In objUser.Delegates 
 216:            DisplayText "Delegate(CDOEXM)",  strDelegate 
 217:        Next 
 ...: 
 220:        ' At this level of the object hierarchy, you can create, move, 
 221:        ' or delete a mailbox from the mailbox store. 
 222:        ' objUser.CreateMailBox   
 223:        ' objUser.DeleteMailBox   
 224:        ' objUser.MoveMailBox   
 ...: 
 ...: 
 249:End Function

Note Lines 222, 223, and 224 are commented out. They provide information only. The script does not create, delete, or move mailboxes when executed.

Table 4 The CDOEXM interface for mailbox management

DaysBeforeGarbageCollection (rw)

The number of days deleted mail is retained before it is permanently deleted (lines 200 and 201).

Delegates (rw)

The list of URLs of all users that have access to the mailbox (lines 215 to 217). Note that the Delegates property is a multivalued property containing the delegated users' distinguished name.

EnableStoreDefaults (rw)

Dictates whether to use only default store values for storage limits, or to use other properties pertaining to the mailbox (lines 202 to 203).

GarbageCollectOnlyAfterBackup (rw)

Dictates whether deleted messages can only be permanently deleted after the mailbox has been backed up (lines 201 and 205).

Hardlimit (rw)

The maximum size of the mailbox, in kilobytes (KB). Over this limit, sending and receiving are restricted (line 206).

HomeMDB (ro)

The URL of the store for the recipient (line 207).

OverQuotaLimit (rw)

The maximum size, in KB, of the StoreQuota property size. Over this limit, sending mail is disabled (line 208).

OverrideStoreGarbageCollection (rw)

Dictates whether the store should be prevented from permanently deleting deleted messages (lines 209 and 210).

RecipientLimit (rw)

Maximum number of people to whom the recipient can send e-mail (per message) (line 211).

StoreQuota (rw)

The maximum size, in KB, allowed for the mailbox (line 212).

CreateMailBox

Creates a mailbox at a specified URL (line 222).

DeleteMailBox

Deletes a mailbox at a specified URL (line 223).

MoveMailBox

Moves a mailbox to a specified URL (line 224).

Management Point 6 Retrieving mailbox properties with CDOEXM from ADSI 

At this stage of the script:

  • You know the configuration of the user mailbox (quota, mailbox cleanup process, mailbox restrictions).

  • You can review and create a list of users with delegated access to the mailbox.

  • You can create, move, and delete mailboxes from the ADSI.User object.

See points 8 and 9 in Figure 3 to locate this operation in the Exchange 2000 logical view.

Retrieving Mailbox-Enabled Object Properties with CDOEX

This section refers to points G and H in Figure 3. 

The EnumMailboxStore function in Sample 12 is the same as the EnumMailboxStore in Sample 10, with one exception. The Sample 12 version retrieves the mailbox-enabled object properties from a CDO.Person object. The Sample 10 version retrieves the mailbox-enabled object properties from an ADSI.User object.

The purpose of this section is to demonstrate how these two different COM technologies are tied together in Exchange 2000, and to compare the kinds of information each provides.

In Sample 6, lines 14 and 15 can be substituted with lines 16 and 17. If lines 16 and 17 are used, the script uses CDOEX instead of ADSI. In both cases, the CDOEXM.MailboxStore interface accesses the mailbox configuration settings.

Lines 30 to 38 of Sample 12 are the same as lines 30 to 38 of Sample 10. In both cases, an ADSI query retrieves the list of mailboxes. The difference is how the mailbox-enabled object information is retrieved from Active Directory. In Sample 10, an ADSI.User object is instantiated at line 56 in the "For Each" loop. In Sample 12, a CDO.Person object is instantiated at line 52. Then the IDataSource interface is opened to retrieve the user information (line 56).

Note that the information used to access user data is the same. In line 31, both script samples use the ADsPath returned by the query result. Behind the scenes, the CDO.Person object uses ADSI. This is why this operation is coupled with ADSI at step G in Figure 3.

Sample 10 retrieves the mailNickName and the displayName attributes in the same way. Unlike Sample 12, however, Sample 10 uses a Fields collection in lines 59 and 60. The CDO.Person object does not expose the mailNickName and the displayName attributes.

CDOEX is linked to ADO in the Exchange 2000 architecture, and ADO exposes a Fields collection (defined in the Exchange store) containing various properties related to the opened data source. Therefore, you can use this method from CDOEX to retrieve LDAP properties contained in the Fields collection.

With the help of the Exchange store architecture and its OLE DB provider, ADO extends the number of properties available from a CDOEX object. The main difference between the properties retrieved from the CDOEX object itself and equivalent properties from the Fields collection is that the ADO Fields collection does not contain any business logic. This is why you should use the CDOEX properties to get or set values whenever possible. Only use the ADO Fields collection when the property is not available from the CDOEX object (as is the case for mailNickName and displayName).

Sample 12 Retrieving a mailbox and its associated mailbox-enabled object properties from CDOEX 

   1:' VBScript function to enumerate the mailboxes in a mailbox store from   
   2:' a CDO.Person object class.                                             
   .: 
   9:Option Explicit 
  10: 
  11:' ------------------------------------------------------------------------ 
  12:Function EnumMailboxStore (urlMailboxStoreDB) 
  ..: 
  ..: 
  26:    ' ---------------------------------------------------------------- 
  27:    WScript.Echo Space (intX + 1) & _ 
  28:             "Retrieving Person/mailbox list via 'ADSI/ADO' LDAP Query." 
  29: 
  30:    ' Search Active Directory for the list of user mailboxes. 
  31:    Set objResultList = ADSearch ("LDAP://" & strDefaultDomainNC, _ 
  32:                                  "(homeMDB=" & urlMailboxStoreDB & ")", _ 
  33:                                  "ADsPath", _ 
  34:                                  "subTree", _ 
  35:                                  False) 
  36: 
  37:    ' ---------------------------------------------------------------- 
  38:    objResult = objResultList.Items 
  ..: 
  ..: 
  52:    Set objPerson = CreateObject ("CDO.Person") 
  53: 
  54:    For intIndice = 1 to (objResultList.Count - 1) 
  55: 
  56:        objPerson.DataSource.Open objResult (intIndice) 
  57: 
  58:        ' Getting two properties for display facilities. 
  59:        strAlias = objPerson.Fields("mailNickName") 
  60:        strDisplayName = objPerson.Fields("displayName") 
  ..: 
  ..: 
  71:        DisplayText "Company(CDOEX)",           objPerson.Company 
  72:        DisplayText "Email(CDOEX)",             objPerson.Email 
  73:        DisplayText "Email2(CDOEX)",            objPerson.Email2 
  74:        DisplayText "Email3(CDOEX)",            objPerson.Email3 
  75:        DisplayText "EmailAddresses(CDOEX)",    objPerson.EmailAddresses 
  76:        DisplayText "FileAs(CDOEX)",            objPerson.FileAs 
  77:        DisplayText "FileAsMapping(CDOEX)",     objPerson.FileAsMapping 
  78:        DisplayText "FirstName(CDOEX)",         objPerson.FirstName 
  79:        DisplayText "HomeCity(CDOEX)",          objPerson.HomeCity 
  80:        DisplayText "HomeCountry(CDOEX)",       objPerson.HomeCountry 
  81:        DisplayText "HomeFax(CDOEX)",           objPerson.HomeFax 
  82:        DisplayText "HomePhone(CDOEX)",         objPerson.HomePhone 
  83:        DisplayText "HomePostalAddress(CDOEX)", _ 
                         objPerson.HomePostalAddress 
  84:        DisplayText "HomePostalCode(CDOEX)", _ 
                         objPerson.HomePostalCode 
  85:        DisplayText "HomePostOfficeBox(CDOEX)", _ 
                         objPerson.HomePostOfficeBox 
  86:        DisplayText "HomeState(CDOEX)",         objPerson.HomeState 
  87:        DisplayText "HomeStreet(CDOEX)",        objPerson.HomeStreet 
  88:        DisplayText "Initials(CDOEX)",          objPerson.Initials 
  89:        DisplayText "LastName(CDOEX)",          objPerson.LastName 
  90:        DisplayText "MailingAddress(CDOEX)",    objPerson.MailingAddress 
  91:        DisplayText "MailingAddressID(CDOEX)",  objPerson.MailingAddressID 
  92:        DisplayText "MiddleName(CDOEX)",        objPerson.MiddleName 
  93:        DisplayText "MobilePhone(CDOEX)",       objPerson.MobilePhone 
  94:        DisplayText "NamePrefix(CDOEX)",        objPerson.NamePrefix 
  95:        DisplayText "NameSuffix(CDOEX)",        objPerson.NameSuffix 
  96:        DisplayText "Title(CDOEX)",             objPerson.Title 
  97:        DisplayText "WorkCity(CDOEX)",          objPerson.WorkCity 
  98:        DisplayText "WorkCountry(CDOEX)",       objPerson.WorkCountry 
  99:        DisplayText "WorkFax(CDOEX)",           objPerson.WorkFax 
 100:        DisplayText "WorkPager(CDOEX)",         objPerson.WorkPager 
 101:        DisplayText "WorkPhone(CDOEX)",         objPerson.WorkPhone 
 102:        DisplayText "WorkPostalAddress(CDOEX)", _ 
                         objPerson.WorkPostalAddress 
 103:        DisplayText "WorkPostalCode(CDOEX)",    objPerson.WorkPostalCode 
 104:        DisplayText "WorkPostOfficeBox(CDOEX)", _ 
                         objPerson.WorkPostOfficeBox 
 105:        DisplayText "WorkState(CDOEX)",         objPerson.WorkState 
 106:        DisplayText "WorkStreet(CDOEX)",        objPerson.WorkStreet 
 107: 
 108:        ' At this level of the object hierarchy, you can obtain 
 109:        ' a stream containing the CDO.Person Vcard. 
 110:        ' objPerson.GetVCardStream. 
 111: 
 112:        If cEnumFieldsCollection Then _ 
 113:                                 EnumFieldsCollection objPerson.Fields 
 ...: 
 ...:

After the data source is opened (line 56), the script enumerates all the properties associated with the CDO.Person object (lines 71 to 106). For more information about the CDO.Person object, see the Exchange SDK at https://msdn.microsoft.com/exchange.

Lines 112 and 113 retrieve the ADO Fields collection associated with an Exchange 2000 server. See Sample 24 for the EnumFieldsCollection function.

Management Point 7 Retrieving mailbox-enabled object properties with CDOEX 

At this stage of the script:

  • You have the property list of the mailbox-enabled object.

  • You can get access to the vCard stream of the CDO.Person object.

See points G and H in Figure 3 to locate this operation in the Exchange 2000 logical view.

Retrieving Mailbox Properties with CDOEXM from CDOEX

This section refers to points I and J in Figure 3. 

To enumerate mailbox properties, the script must first invoke the GetInterface method of the CDO.Person object (see Sample 13, lines 127 to 139). The GetInterface method is a generic interface navigation aid for scripting languages that do not support such navigation directly. Which interface names are valid to pass to GetInterface depends on the specific implementation.

The IMailboxStore interface is aggregated by two objects:

  • CDO.Person

  • ADSI.User

For example, at line 56 of Sample 12, the CDO.Person object is used as follows:

ObjPerson.DataSource.Open objResult (intIndice)

This is possible because the IDataSource interface is directly accessible from the CDO.Person object. You can get the same results with the following code:

objDataSource = ObjPerson.GetInterface("IDataSource") 
objDataSource.Open objResult (intIndice)

This method is less efficient. To access the mailbox settings (this is done with ADSI in lines 200 to 217 of Sample 11), the script in Sample 13 "connects" the CDO.Person object to the IMailboxStore interface. This is necessary because the IMailboxStore interface is not directly accessible from the CDO.Person object. Line 125 makes the connection:

objMailbox = ObjPerson.GetInterface("IMailboxStore ")

The steps from lines 127 to 144 in Sample 13 (CDOEX) are exactly the same as lines 200 to 217 in Sample 11 (ADSI). Only the base object is different.

Sample 13 Retrieving mailbox properties with CDOEXM from CDOEX 

 ...: 
 ...: 
 125:        Set objMailbox = objPerson.GetInterface ("IMailboxStore ") 
 126: 
 127:        DisplayText "DaysBeforeGarbageCollection(CDOEXM)", _ 
 128:                     objMailbox.DaysBeforeGarbageCollection 
 129:        DisplayText "EnableStoreDefaults(CDOEXM)", _ 
 130:                     objMailbox.EnableStoreDefaults 
 131:        DisplayText "GarbageCollectOnlyAfterBackup(CDOEXM)", _ 
 132:                     objMailbox.GarbageCollectOnlyAfterBackup 
 133:        DisplayText "Hardlimit(CDOEXM)", objMailbox.Hardlimit 
 134:        DisplayText "HomeMDB(CDOEXM)", objMailbox.HomeMDB 
 135:        DisplayText "OverQuotaLimit(CDOEXM)", objMailbox.OverQuotaLimit 
 136:        DisplayText "OverrideStoreGarbageCollection(CDOEXM)", _ 
 137:                    objMailbox.OverrideStoreGarbageCollection 
 138:        DisplayText "RecipientLimit(CDOEXM)", objMailbox.RecipientLimit 
 139:        DisplayText "StoreQuota(CDOEXM)", objMailbox.StoreQuota 
 ...: 
 ...: 
 142:        For Each strDelegate In objMailbox.Delegates 
 143:            DisplayText "Delegate(CDOEXM)",  strDelegate 
 144:        Next 
 ...: 
 147:        ' At this level of the object hierarchy, you can create, 
 148:        ' move, or delete a mailbox from the mailbox store. 
 149:        ' objMailbox.CreateMailBox   
 150:        ' objMailbox.DeleteMailBox   
 151:        ' objMailbox.MoveMailBox   
 ...: 
 ...: 
 179:End Function

Management Point 8 Retrieving the mailbox properties with CDOEXM from CDOEX 

At this stage of the script:

  • You know the configuration of the mailbox-enabled Active Directory object (quota, mailbox cleanup process, mailbox restrictions).

  • You can review and create a list of users with delegated access to this mailbox.

  • You can create, move, or delete a mailbox from the CDO.Person object.

See points I and J in Figure 3 to locate this operation in the Exchange 2000 logical view.

Retrieving Mail-Enabled Object Properties with CDOEXM from ADSI or CDOEX

This section refers to points 7, 8, G, and H in Figure 3. 

The list returned by the LDAP query contains mailbox-enabled recipients because the script accesses the homeMDB attribute of the mailbox store (see Sample 10 and Sample 12, lines 31 to 35). At this level of the object hierarchy, it is also possible to retrieve the mail-enabled recipient properties if the query returns a mail-enabled recipient list.

The purpose of the EnumAllInXL.wsf script is to decompose the Exchange 2000 COM object hierarchy. Decomposing a mail-enabled object will stop the exploration at this level because a mail-enabled object does not have a mailbox.

To access the mail-enabled object's properties, the script must use the IMailRecipient interface instead of IMailboxStore.

Table 5 The CDOEXM interface for mail-enabled recipients management

Alias (rw)

The e-mail alias of the recipient.

ForwardingStyle (rw)

Specifies whether mail is also delivered to an alternative mail address.

ForwardTo (rw)

The mailbox recipient where mail is forwarded.

HideFromAddressBook (rw)

Specifies whether the recipient is displayed in the address book.

IncomingLimit (rw)

Specifies the maximum size, in KB, of a message sent to this recipient.

OutgoingLimit (rw)

Specifies the maximum size, in KB, of a message that this user can send.

ProxyAddresses (rw)

A list of proxy addresses for the recipient.

RestrictedAddresses (rw)

Specifies whether messages from the addresses listed in RestrictedAddressList are to be accepted or rejected.

RestrictedAddressList (rw)

Specifies a list of addresses to accept or reject.

SMTPEmail (rw)

Specifies the SMTP address used for the recipient.

TargetAddress (rw)

Specifies the delivery address to which the e-mail for this recipient should be sent.

X400Email (rw)

The X.400 address used for the recipient.

MailDisable

Disables mail to a recipient.

MailEnable

Enables a recipient to receive mail.

The following are two scripts that create a mail-enabled Active Directory user with the IMailRecipient interface. Sample 14 creates a mail-enabled user from an ADSI object. Sample 15 creates a mail-enabled user from a CDOEX object.

Sample 14 Creating a mail-enabled object with CDOEXM from ADSI 

   1:' Script creating a user and enabling their mail address using the CDOEXM  
   2:' IMailRecipient interface associated with the ADSI user object class. 
   .: 
   9:Option Explicit 
  10: 
  11:Const cEmailName                = "bradley.beck" 
  12:Const cFirstName                = "Bradley" 
  13:Const cLastName                 = "BECK" 
  14:Const cUserID                   = "BeckB" 
  ..: 
  ..: 
  ..: 
  19:' By default, this script must be run on the local Exchange server. 
  20:Set WNetwork = Wscript.CreateObject("Wscript.Network") 
  21:strComputerName = WNetwork.ComputerName 
  22:Wscript.DisconnectObject (WNetwork) 
  23:Set WNetwork = Nothing 
  ..: 
  ..: 
  28:' Get the default Windows 2000 domain name. 
  29:Wscript.Echo "Binding to RootDSE to get default Domain Name." 
  30:Set objRoot = GetObject("LDAP://RootDSE") 
  31:strDefaultDomainNC = objRoot.Get("DefaultNamingContext") 
  32:Set objRoot = Nothing 
  ..: 
  ..: 
  37:Set objContainer = GetObject("LDAP://" & strComputerName & _ 
  38:                             "/" & "CN=users," & strDefaultDomainNC) 
  39: 
  40:Set objUser = objContainer.Create ("user", _ 
  41:                                   "cn=" & UCase (cLastName) & _ 
  42:                                   " " & cFirstName) 
  43:objUser.Put "samAccountName", cUserID 
  44:objUser.SetInfo 
  45: 
  46:objUser.Put "sn", cLastName 
  47:objUser.Put "givenName", cFirstName 
  48:objUser.Put "userPrincipalName", cEmailName 
  49:objUser.Put "displayName", UCase (cLastName) & " " & cFirstName 
  50: 
  51:objUser.AccountDisabled = False 
  52:objUser.SetInfo 
  53: 
  54:objUser.SetPassword "password" 
  55: 
  56:objUser.MailEnable "SMTP:" & cEmailName & "@Example.Com" 
  57:objUser.SetInfo 
  ..: 
  ..: 
  ..:

To access the IMailRecipient interface, Sample 15 uses the same GetInterface method (line 38) used in Sample 13 (line 125). The logic and the reasons are exactly the same.

The IMailRecipient interface is aggregated by the following objects:

  • CDO.Person

  • CDO.Folder

  • ADSI.Contact

  • ADSI.Group

  • ADSI.User

The IMailRecipient interface is used in Sample 22 to mail-enable an Exchange store folder.

Sample 15 Creating a mail-enabled object with CDOEXM from CDOEX 

   1:' Script creating a user and enabling their mail address using the CDOEXM 
   2:' IMailRecipient interface associated with the CDO person object class.   
   .: 
   9:Option Explicit 
  10: 
  11:Const cEmailName                = "bradley.beck" 
  12:Const cFirstName                = "Bradley" 
  13:Const cLastName                 = "BECK" 
  14:Const cUserID                   = "BeckB" 
  ..: 
  ..: 
  19:' By default, this script must be run on the local Exchange server. 
  20:Set WNetwork = Wscript.CreateObject("Wscript.Network") 
  21:strComputerName = WNetwork.ComputerName 
  22:Wscript.DisconnectObject (WNetwork) 
  23:Set WNetwork = Nothing 
  ..: 
  ..: 
  28:' Get the default Windows 2000 domain name. 
  29:Wscript.Echo "Binding to RootDSE to get default Domain Name." 
  30:Set objRoot = GetObject("LDAP://RootDSE") 
  31:strDefaultDomainNC = objRoot.Get("DefaultNamingContext") 
  32:Set objRoot = Nothing 
  ..: 
  ..: 
  37:Set objPerson = CreateObject ("CDO.Person") 
  38:Set objMailRecipient = objPerson.GetInterface ("IMailRecipient") 
  39: 
  40:objPerson.Fields("samAccountName") = cUserID 
  41:objPerson.LastName = cLastName 
  42:objPerson.FirstName = cFirstName 
  43:objPerson.Fields("userPrincipalName") = cEmailName 
  44:objPerson.Fields("displayName") = UCase (cLastName) & " " & cFirstName 
  45:objPerson.Fields("userAccountControl") = 512 
  46:objPerson.Fields("userPassword") = "password" 
  47:objPerson.Fields.Update 
  48:objPerson.DataSource.SaveTo "LDAP://" & strComputerName & "/" & _ 
  49:                            "cn=" & cEmailName & "," & _ 
  50:                            "cn=users," & strDefaultDomainNC 
  51: 
  52:objMailRecipient.MailEnable "SMTP:" & cEmailName & "@Example.Com" 
  53: 
  54:objPerson.DataSource.Save 
  ..: 
  ..:

Management Point 9 Retrieving the mail-enabled object properties with CDOEXM from ADSI or CDOEX 

At this stage of the script:

  • You know the configuration of the mail-enabled object.

  • You know the e-mail address of the recipient.

  • You can enable or disable mail-enabled recipients from the CDO.Person object or the ADSI.User object.

See points 8, 9, I, and J in Figure 3 to locate this operation in the Exchange 2000 logical view.

So far, the script has retrieved the list of mailboxes in the mailbox store and the associated configurations for each. The next step is to look at the mailbox content (various data object types, such as messages).

Before decomposing the mailbox, look at the way public stores are retrieved from storage groups. The method is almost the same as the one used to retrieve mailbox stores (see Sample 9), so it makes sense to look at them together.

Getting Information About Public Folders

In addition to mailbox stores, storage groups contain public stores, which contain public folders.

Retrieving Exchange 2000 Public Store Information with CDOEXM

This section refers to points 12 and M in Figure 3. 

Line 257 of Sample 8 calls the EnumPublicStoreDBs function with the CDOEXM.StorageGroup object as a parameter. This function retrieves the list of public stores in the storage group.

The list of public stores is returned in a collection (see Sample 16, line 337). The "For Each" loop opens a data source (line 341) that corresponds to the distinguished name retrieved from the collection. For each public store, the script extracts the values of the object's properties (lines 348 to 362).

Sample 16 Retrieving public store information with CDOEXM 

 330:Private Function EnumPublicStoreDBs (objStorageGroup) 
 ...: 
 ...: 
 335:        Set objPublicStoreDB = CreateObject("CDOEXM.PublicStoreDB") 
 336: 
 337:        For Each urlPublicStoreDB In objStorageGroup.PublicStoreDBs 
 338:            DisplayText "urlPublicStoreDB(CDOEXM)", urlPublicStoreDB 
 ...: 
 ...: 
 341:            objPublicStoreDB.DataSource.Open (urlPublicStoreDB) 
 ...: 
 ...: 
 348:            DisplayText "Name(CDOEXM)", objPublicStoreDB.Name 
 349:            DisplayText "DaysBeforeGarbageCollection", _ 
 350:                        objPublicStoreDB.DaysBeforeGarbageCollection 
 351:            DisplayText "DaysBeforeItemExpiration", _ 
 352:                        objPublicStoreDB.DaysBeforeItemExpiration 
 353:            DisplayText "GarbageCollectOnlyAfterBackup", _ 
 354:                        objPublicStoreDB.GarbageCollectOnlyAfterBackup 
 355:            DisplayText "DBPath", objPublicStoreDB.DBPath 
 356:            DisplayText "SLVPath", objPublicStoreDB.SLVPath 
 357:            DisplayText "FolderTree", objPublicStoreDB.FolderTree 
 358:            DisplayText "Status", objPublicStoreDB.Status 
 359:            DisplayText "Enabled", objPublicStoreDB.Enabled 
 360:            DisplayText "StoreQuota", objPublicStoreDB.StoreQuota 
 361:            DisplayText "HardLimit", objPublicStoreDB.HardLimit 
 362:            DisplayText "ItemSizeLimit", objPublicStoreDB.ItemSizeLimit 
 363: 
 364:            ' At this level of the object hierarchy, 
 365:            ' you can mount/dismount the public store. 
 366:            ' objPublicStoreDB.Dismount 
 367:            ' objPublicStoreDB.Mount 
 368: 
 369:            ' At this level of the object hierarchy, 
 370:            ' you can move the public store 
 371:            ' to another location. 
 372:            ' objPublicStoreDB.MoveDataFiles 
 373: 
 374:            If cEnumFieldsCollection Then _ 
 375:               EnumFieldsCollection objPublicStoreDB.Fields 
 376: 
 377:               EnumFolderTree objPublicStoreDB.FolderTree 
 ...: 
 ...: 
 381:       Next 
 ...: 
 ...: 
 386:End Function

Note Lines 366, 367, and 372 are commented out. They provide information only. The script will not mount, dismount, or move data files when executed.

Table 6 The CDOEXM object to manage the Exchange public store

Name (rw)

The name of the object (line 348).

FolderTree (rw)

The URL of the public folder tree that is associated with this database (lines 357 and 377).

DBPath (ro)

The path to the Exchange database file (line 355).

SLVPath (ro)

The path to the Exchange streaming database file (line 356).

Status (ro)

The current online status of the database (line 358).

Enabled (rw)

Indicates whether this store should be mounted at start-up (line 359).

StoreQuota (rw)

The quota limit at which folders receive warning messages (line 360).

HardLimit (rw)

The quota limit at which messages are rejected (line 361).

ItemSizeLimit (rw)

The maximum size of a message, in KB (line 362).

DaysBeforeItemExpiration (rw)

The age limit for all folders in this store, in days (lines 351 and 352).

DaysBeforeGarbageCollection (rw)

The number of days to keep deleted items (lines 349 and 350).

GarbageCollectOnlyAfterBackup (rw)

Indicates whether items should not be permanently deleted until the store has been backed up (lines 353 and 354).

MoveDataFiles

Changes the database file paths. DBPath and SLVPath parameters are file paths on the server. Flags parameter is ignored. For an existing object, the path can only be changed if running on the local machine (line 372).

Mount

Mounts this store for use. Time-out parameter is ignored (line 367).

Dismount

Dismounts this store. Time-out parameter is ignored (line 366).

GetInterface

Returns the specified interface on the object.

DataSource (ro)

Returns the IDataSource interface on the object (line 341).

Fields (ro)

Returns the Fields collection for the object (lines 374 and 375).

Lines 374 and 375 retrieve the ActiveX® Data Objects (ADO) Fields collection associated with a mailbox store. See Sample 24 for the EnumFieldsCollection function.

Management Point 10 Retrieving Exchange 2000 public store information with CDOEXM 

At this stage of the script:

  • You have an interface to retrieve the public folder tree name of the public store with the help of the IFolderTree CDOEXM interface.

  • You know whether the public store is mounted or dismounted and its default state at the startup of Exchange.

  • You have a set of information about the public store quotas.

  • You have a set of information about the public store cleanup process.

  • You can mount and dismount the public store and move the public store data files to another location.

See points 12 and M in Figure 3 to locate this operation in the Exchange 2000 logical view.

Retrieving Public Folder Tree Properties

This section refers to points 13 and N in Figure 3. 

Line 56 of Sample 16 shows the URL of the public folder tree associated with a public store. The same parameter is used at line 377 to call the EnumFolderTree function.

To retrieve public folder tree information, the script opens the data source (Sample 17, line 398) that corresponds to the distinguished name retrieved from the public folder property (Sample 16, lines 357 and 377). The script then extracts the values of properties in the retrieved public folder tree (lines 405 to 418).

Sample 17 Retrieving public folder tree properties 

 389:Private Function EnumFolderTree (urlFolderTree) 
 ...: 
 ...: 
 394:        Set objFolderTree = CreateObject("CDOEXM.FolderTree") 
 395: 
 396:        DisplayText "FolderTree(CDOEXM)", urlFolderTree 
 397: 
 398:        objFolderTree.DataSource.Open (urlFolderTree) 
 ...: 
 ...: 
 405:        DisplayText "Name(CDOEXM)", objFolderTree.Name 
 ...: 
 ...: 
 409:        For Each strReplica in objFolderTree.StoreDBs 
 410:            DisplayText "Replicas(CDOEXM)", strReplica 
 411:        Next 
 ...: 
 ...: 
 415:        DisplayText "TreeType(CDOEXM)", objFolderTree.TreeType 
 416:        DisplayText "RootFolderUrl(CDOEXM)", objFolderTree.RootFolderUrl 
 417: 
 418:        If cEnumFieldsCollection Then _ 
                EnumFieldsCollection objFolderTree.Fields 
 419: 
 420:           EnumPublicStore objFolderTree.Name 
 ...: 
 ...: 
 425:End Function

Table 7 The CDOEXM object to manage Exchange folder trees

Name (rw)

The name of the object (line 405).

StoreDBs (ro)

The list of URLs of public store databases that contain replicas of this public folder tree (lines 409 to 411).

TreeType (ro)

The public folder tree type (line 415).
0=General Purpose, 1=MAPI

RootFolderURL (ro)

The URL that should be used to bind ADO to the root folder of this public folder tree (line 416).

GetInterface

Returns the specified interface on the object.

DataSource (ro)

Returns the IDataSource interface on the object (line 398).

Fields (ro)

Returns the Fields collection for the object (line 418).

Line 418 retrieves the ADO Fields collection associated with a public folder tree. See Sample 24 for the EnumFieldsCollection function.

Management Point 11 Retrieving public folder tree properties with CDOEXM 

At this stage of the script:

  • You have public folder tree information for a public store.

  • You know the tree type of the root folder tree and other tree properties.

  • You have the RootFolderURL to the root public folder of the folder tree.

See points 13 and N in Figure 3 to locate this operation in the Exchange 2000 logical view.

Line 420 of Sample 17 uses the public folder tree name as a parameter to explore the public folder tree's content. This is where the script really enters the Exchange store.

Getting Information about Folder Trees in Mailboxes and Public Folders

The following sections discuss exploring the folders inside mailboxes and public folders.

Building the Exchange Store Path to Explore the Content of a Mailbox Folder

This section refers to points 10 and K in Figure 3. 

Sample 18 continues the script in Sample 11, which accesses a mailbox from an ADSI.User object.

Sample 19 continues the script in Sample 13, which accesses a mailbox from a CDO.Person object.

Whatever the origin of the object holding the mailbox configuration, both scripts use the same method to access the mailbox content.

First, the script tests the user's identity. Because the Exchange store security model does not, by default, allow access to the content of all mailboxes, the user who is currently logged on has access to his or her mailbox only. The script does not work unless Microsoft® Windows® 2000 and Microsoft® Exchange 2000 Server have been installed with a user name identical to the mailbox alias (see Sample 18, line 231, and Sample 19, line 160).

If this is the case, the BrowseStoreFolder function can be called. The parameter passed to this function is the file://./backofficestorage URL that opens the Exchange store.

Note All scripts in this document must be run on an Exchange 2000 server. No remote access to the Exchange store is possible through the Exchange 2000 OLE DB Provider.

You can also access the Exchange store through the Installable File System (IFS) by browsing the file system folder tree (see Figure 4). This method is practicable, but only the stream of each item is accessible and item content is not parsed. To access item properties, an alternate mechanism must be used, such as the World Wide Web Distributed Authoring and Versioning (WebDAV) protocol, the Exchange OLE DB provider (ExOLEDB) with ADO 2.5, CDO for Exchange 2000 (CDOEX), or the Messaging API (MAPI).

Cc750307.ex2kw04(en-us,TechNet.10).gif

Figure 4 The Exchange store file system

The file://./backofficestorage URL for a mailbox has the form:

File://./backofficestorage/Example.Com/MBX/MailboxAlias

This URL is constructed at line 236 of Sample 18 and line 165 of Sample 19. The MailboxAlias is retrieved at lines 60 and 61 of Sample 10 for ADSI and at lines 59 and 60 of Sample 12 for CDOEX.

Example.Com is the canonicalName LDAP property name retrieved at lines 89 and 90 of Sample 6.

The BrowseStoreFolder function is called with the file://./backofficestorage URL as a parameter.

Sample 18 Building the Exchange store pointer to explore the folder content in a mailbox through ADSI 

 ...: 
 ...:  
 220:    ' At this level of the object hierarchy, you can create, 
 221:    ' move, or delete a mailbox from the mailbox store. 
 222:    ' objUser.CreateMailBox   
 223:    ' objUser.DeleteMailBox   
 224:    ' objUser.MoveMailBox   
 225: 
 226:    ' -------------------------------------------------------------------- 
 227:    ' Access the mailbox of the user who is currently logged on. 
 228:    ' This will not be a security problem; the mailbox is owned by the 
         ' user running the script, because the user name is used for the  
         ' e-mail alias. 
 229:    ' CONDITION: The mailbox alias and the user name must be the 
 230:    ' same, because you get the current user name (account name) from WSH. 
 231:    If strCurrentUserName = strAlias And cEnumMaiboxTree Then 
 232: 
 233:       WScript.Echo Space (intX) & "Browsing mailbox '" & _ 
                                        strDisplayName & "'." 
 234: 
 235:       ' Construct the file://./backofficestorage/ pointer for the mailbox 
 236:       BrowseStoreFolder "file://./backofficestorage/" & _ 
 237:                         strCanonicalNameDefaultDomain & "MBX/" & strAlias 
 238: 
 239:    End If 
 ...: 
 ...: 
 243:Next 
 ...: 
 ...: 
 249:End Function

Sample 19 Building the Exchange store pointer to explore the folder content in a mailbox through CDOEX 

 ...: 
 ...: 
 147:    ' At this level of the object hierarchy, you can create, 
 148:    ' move, or delete a mailbox from the mailbox store. 
 149:    ' objMailbox.CreateMailBox   
 150:    ' objMailbox.DeleteMailBox   
 151:    ' objMailbox.MoveMailBox   
 152: 
 153:    Set objMailbox = Nothing 
 154: 
 155:    ' -------------------------------------------------------------------- 
 156:    ' Access the mailbox of the user who is currently logged on. 
 157:    ' This will not be a security problem; the mailbox is owned by the  
 158:    ' user running the script, because the user name is used for the 
 159:    ' mailbox alias. 
         ' CONDITION: The mailbox alias and the user name must be the same 
         ' because you get the current user name (account name) from WSH.  
 160:    If strCurrentUserName = strAlias And cEnumMaiboxTree Then 
 161: 
 162:       WScript.Echo Space (intX) & "Browsing mailbox '" & _  
                                        strDisplayName & "'." 
 163: 
 164:       ' Construct the file://./backofficestorage/ pointer for the mailbox 
 165:       BrowseStoreFolder "file://./backofficestorage/" & _ 
 166:                         strCanonicalNameDefaultDomain & "MBX/" & strAlias 
 167: 
 168:    End If 
 ...: 
 ...: 
 172:Next 
 ...: 
 ...: 
 179:End Function
Building the Exchange Store Pointer to Explore Folder Content in a Public Folder Hierarchy

This section refers to points 14 and O in Figure 3. 

Sample 20 uses the same logic as the method that constructs the file://./backofficestorage URL for the mailbox. The only difference is the use of the folder tree name. See line 377 of Sample 16. This part of the code determines which folder tree the public folder store belongs to.

Note The RootFolderURL property, available from the CDOEXM.FolderTree object, can be used to bind ADO to the root folder of the public store (see Table 7). However, to reuse the logic for retrieving mailbox content, this script does not use the RootFolderURL*.* Instead it builds the file://./backofficestorage URL in the same way as in Sample 18 and Sample 19.

The folder tree name is a distinguished name used as a data source at line 398 of Sample 17. In Sample 17, the name of the folder tree is used as a parameter to call the EnumPublicStore function (line 420).

The form of the file://./backofficestorage URL for a public folder is:

file://./backofficestorage/Example.Com/PublicFolderTreeName

This URL is constructed at lines 435 and 436.

Note In a real production environment, this script collects a huge amount of data from the public folder hierarchy. For educational purposes, you should run this script from an Exchange 2000 server in a test installation.

Next, the BrowseStoreFolder function is called with the file://./backofficestorage URL as a parameter.

Sample 20 Building the Exchange store pointer to explore the content of folders in a public folder hierarchy 

 428:Private Function EnumPublicStore (strFolderTreeName) 
 429: 
 430:    If cEnumFolderTree Then 
 431:       intX = intX + 1 
 432:       WScript.Echo Space (intX) & "Browsing '" & strFolderTreeName & "'"  
 433: 
 434:       ' Construct the file://./backofficestorage/ pointer for the 
        '  public folder. 
 435:       BrowseStoreFolder "file://./backofficestorage/" & _ 
 436:                      strCanonicalNameDefaultDomain & strFolderTreeName 
 437:       intX = intX - 1 
 438:    End If 
 439: 
 440:End Function
Exploring Folder Content

This section refers to points 10, 14, K, and O in Figure 3. 

The BrowseStoreFolder function has the file://./backofficestorage URL as a parameter. This parameter is contained in the strURL variable and is an entry-point into the Exchange store (see line 18 of Sample 21).

Note Under Exchange 5.5, the access method to the Information Store was based on MAPI and made use of CDO 1.21. This client/server access method is still supported under Exchange 2000, but because many scripts and applications run locally on the server, Exchange 2000 provides a local access method as well.

When running locally, an application or a script can access the Exchange store with ADO 2.5. CDOEX is not designed for this type of access. It has all the objects and interfaces needed to access, manage, and decompose items in the Exchange store, but it does not have a navigation model for exploration. ADO 2.5 performs this function.

Lines 16 and 18 of Sample 21 present a simplified method for opening the Exchange store. This simplicity hides the fact that many parameters are assumed for the ADO connection.

The following three methods achieve the same results.

Method 1 

Set objRecord = CreateObject("ADODB.Record") 
objRecord.Open strURL

Method 2 

Set objConnection = CreateObject("ADODB.Connection") 
Set objRecord = CreateObject("ADODB.Record") 
objConnection.ConnectionString = strUrl 
objConnection.Provider = "EXOLEDB.DATASOURCE" 
objConnection.Open 
objRecord.Open , objConnection

Method 3 

Set objConnection = CreateObject("ADODB.Connection") 
Set objRecord = CreateObject("ADODB.Record") 
objConnection.Open "Provider=EXOLEDB.DATASOURCE; Data Source=" & strURL & ";" 
objRecord.Open , objConnection

All these methods open the Exchange store in the same way.

Method 1 looks simpler because it assumes certain defaults. The strURL contains a file://./backofficestorage URL. Because the Exchange OLE DB provider is registered in the system for this namespace, there is no need to specify the ExOLEDB provider name. Behind the scenes, the objRecord object creates an objConnection object. This is how objRecord connects the script to the Exchange store.

Method 2 does not assume defaults. Instead, the script explicitly defines which OLE DB provider to use (ExOLEDB). Method 3 uses exactly the same parameters as Method 2, but Method 3 uses an ADO connection string. These methods are less ambiguous than Method 1. With Methods 2 and 3 you are always sure that the script will use ExOLEDB, because ExOLEDB accepts different forms of Exchange store URLs (file://./backofficestorage and https://).

The Exchange store URL does not have to be file://./backofficestorage. You can also use an https://MachineName/Exchange/MailboxAlias URL. The RootFolderURL property from the CDOEXM.FolderTree returns an https:// URL to the root of a public folder tree. In such a case, you must use either Method 2 or Method 3. Method 1 will not work because the default OLE DB provider for this namespace is the Microsoft OLE DB Provider for Internet Publishing (MSDAIPP) and it will not allow a connection to the Exchange store.

Note An https:// URL given to ADO refers by default to MSDAIPP, unless you specify a different OLE DB provider.

MSDAIPP allows developers to access files through OLE DB interfaces on HTTP servers that support the Microsoft® FrontPage® Web Extender Client (WEC) or Web Distributed Authoring and Versioning (WebDAV) protocol extensions. The MSDAIPP provider is currently installed with the full version of Microsoft® Internet Explorer 5, which comes with Office 2000 or can be downloaded separately. MSDAIPP is built on top of WebDAV, and there is no other option for its use. CDOEX is not supported on top of MSDAIPP.

Because the OLE DB provider is assumed rather than specified in Method 1, the namespace of the OLE DB pointer is a determinant. With an https:// URL, the script must use Method 2 or Method 3 because both methods specify an OLE DB provider.

Even if the OLE DB pointer is an https:// URL, you cannot make a remote connection to the Exchange 2000 OLE DB provider. To make remote connections to the Exchange store, either WebDAV or MAPI must be used.

Sample 21 Exploring the Folders content 

  1:' VBScript function to enumerate folder content using ADO 2.5 on an        
  2:' Exchange server by using the file://./backofficestorage pointer.         
  .: 
  9:Option Explicit 
10: 
11:' ------------------------------------------------------------------------- 
12:Private Function BrowseStoreFolder (strURL) 
13: 
14:Dim objRecord 
15: 
16:        Set objRecord = CreateObject("ADODB.Record") 
17: 
18:        objRecord.Open strURL..: 
..: 
22:        LoopInFolder objRecord 
23: 
24:        objRecord.Close 
..: 
..: 
29:End Function 
30: 
31:' -------------------------------------------------------------------------- 
32:Private Function LoopInFolder (objParentRecord) 
..: 
..: 
39:    Set objRecordSet = objParentRecord.GetChildren 
40: 
41:    While Not objRecordSet.EOF 
42: 
43:          Set objChildRecord = CreateObject("ADODB.Record") 
44: 
45:          objChildRecord.Open objRecordSet 
..: 
..: 
54:          If cEnumFieldsCollection Then _ 
                EnumFieldsCollection objChildRecord.Fields 
55: 
56:          If objRecordSet ("DAV:iscollection") Then 
57:             LoopInFolder objChildRecord 
58:          Else 
59:             If objChildRecord ("DAV:contentclass") = _ 
                                   "urn:content-classes:message" Then 
60:                If cEnumMessageContent Then _ 
                      EnumMessageContent objChildRecord("DAV:href") 
61:                End If 
62:          End If 
63: 
64:          objChildRecord.Close 
..: 
..: 
69:          objRecordSet.MoveNext 
70:    Wend 
71: 
72:    objRecordSet.Close 
..: 
..: 
77:End Function

After the Exchange store is opened (line 18), the loop in the folder hierarchy begins with the call to the LoopInFolder function (line 22).

The LoopInFolder function uses some ADO 2.5 enhancements to the navigation model. For example, line 39 uses the GetChildren method to get a recordset of the objects located under the current record.

Note The CopyRecord and MoveRecord* *methods are other useful ADO 2.5 enhancements. These methods enable copy and move operations in the Exchange store. See the ADO 2.5 SDK for more information about these methods and their related options.

The first LoopInFolder call passes the file://./backofficestorage URL, a root pointer to a public folder or mailbox. If file://./backofficestorage points to a public folder, the script gets the list of its items and folders. If it points to a mailbox, the script gets the list of folders contained in the mailbox, such as Inbox, Contacts, Calendar, and so on.

Next, the script loops to examine every item in the objRecordSet object (lines 41 to 70). The script opens each item (line 45) and enumerates its Fields collection (line 54). To determine whether the examined item contains other items (such as folders), the DAV:iscollection property from the Fields collection is tested. This property returns True if the items contain other items. The script then iterates through each item with the LoopInFolder function.

If the item is not a collection, it is a data object, also called a leaf object. A data object can be an e-mail message, a calendar item, a contact item, a document, and so on. Line 59 decomposes a message item to illustrate how a message is built and what the message object model offers to facilitate management tasks. The script can be easily modified to examine a calendar item or a contact item.

Every item discovered in the loop has a content class property in its Fields collection. Therefore, you can review the content type and other properties of each item in the Excel spreadsheet (line 54).

After the item is explored, the script gets the next one (line 69) and the loop continues.

The Fields collection DAV:href property is passed as a parameter that corresponds to the content class of an e-mail message. This property contains a file://./backofficestorage URL to the message. See Sample 23 to examine the content of an e-mail message.

Management Point 12 The Folder tree exploration 

At this stage of the script:

  • You've retrieved the content of a folder and its children with the GetChildren method.

  • You can move or copy a complete folder with its contents and its children with the MoveRecord and CopyRecord methods.

See points 10, 14, K, and O in Figure 3 to locate this operation in the Exchange 2000 logical view.

Performing Simple Exchange Management Tasks with Scripts
Creating Mail-Enabled Folders

This section refers to points 15, 16, 17, P, Q, and R in Figure 3. 

Sample 21 shows how to access an Exchange store folder. It can also be useful to create and then mail-enable folders in a public folder hierarchy.

Mail-enabled folders can receive mail, including alert messages announcing important changes in the server. (See "Monitoring Exchange 2000 Server Activity" later in this document for information about server monitoring.) Alerts are then logged and archived in the public folder.

With the IMailRecipient interface (see also Sample 15), you can enable an e-mail address on a folder in the Exchange store. This is the purpose of Sample 22.

Sample 22 Enabling e-mail on a folder located in a public store 

   1:<!—- VBScript script to create a subfolder in the public folder tree.  --> 
   2:<!-- After the folder is created or opened, e-mail enable this         --> 
     <!-- subfolder.                                                        -->                                        
   .: 
   9:<job> 
  10: 
  11:    <object Id="objConnection" ProgID="ADODB.Connection" Reference=True /> 
  12:    <object Id="objRecord" ProgID="ADODB.Record" Reference=True /> 
  13:    <object Id="objFolder" ProgID="CDO.Folder" Reference=True /> 
  14: 
  15:    <script language="VBScript"> 
  16: 
  17:    Option Explicit 
  18: 
  19:    Const cDomain = "Example.Com" 
  20:    Const cFolderName = "MyMail-EnabledFolder" 
  ..: 
  27:    strURLOpen = "file://./backofficestorage/" & _ 
  28:                 cDomain & "/Public Folders" 
  29:    strURLItem = "file://./backofficestorage/" & _ 
  30:                 cDomain & "/Public Folders/" & cFolderName 
  31: 
  32:    Wscript.Echo strURLOpen 
  33: 
  34:    objConnection.Open "Provider=EXOLEDB.DATASOURCE; Data Source=" & _ 
                                                           strURLOpen & ";" 
  35: 
  36:    Wscript.Echo strURLItem 
  37: 
  38:    objRecord.Open strURLItem, _ 
  39:                   objConnection, _ 
  40:                   adModeReadWrite, _ 
  41:                   adCreateCollection Or adOpenIfExists 
  42: 
  43:    objFolder.DataSource.Open strURLItem 
  44: 
  45:    Set objMailRecipient = objFolder.GetInterface ("IMailRecipient") 
  46: 
  47:    objMailRecipient.MailEnable 
  48:    objFolder.DataSource.Save 
  49: 
  50:    Set objMailRecipient = Nothing 
  51: 
  52:    objRecord.Close 
  53: 
  54:    Wscript.Echo "Completed." 
  55: 
  56:    </script> 
  57:</job>

Sample 23 has three points of interest:

  • It uses an object instantiation capability provided by WSH, instead of the traditional CreateObject function from the Microsoft® Visual Basic® run-time libraries (lines 11, 12, and 13). Note the reference parameter set to True. This allows the retrieval of type library definitions associated with each object. This is why the script can use the three constants at lines 40 and 41 without the Const declaration statement.

  • It uses ADO 2.5 to open a data source on the parent folder level. In the sample case, the data source is the root public folder itself (line 34).

  • The script opens a Record object with the connection established at line 34 to create the desired folder (lines 38 to 41). Note the "adCreateCollection Or adOpenIfExists" statement, which opens a folder if one exists or creates a new one if it doesn't.

After the folder is created (lines 38 to 41), the CDO.Folder object instantiated at line 13 opens a data source with the file://./backofficestorage URL of the new folder (line 43). Next, the CDO.Folder object is aggregated to the IMailRecipient interface (line 45) to enable its e-mail address. The logic is the same as in prior samples (see Sample 14 and Sample 15).

Management Point 13 Enabling e-mail on a folder 

At this stage of the script:

  • You can create a folder.

  • You can mail-enable or mail-disable a folder from the CDO.Folder object.

See points 15, 16, 17, P, Q, and R in Figure 3 to locate this operation in the Exchange 2000 logical view.

Examining Message Content

This section refers to points 11, 18, L, and S in Figure 3. 

The file://./backofficestorage message URL is contained in the strURL variable passed as a parameter to the EnumMessageContent function (see line 21 of Sample 23). CDO for Exchange 2000 (CDOEX) uses this URL in line 27 to access the message item instantiated at line 26. Lines 34 to 52 review the message properties.

Line 57 retrieves the ADO Fields collection associated with the CDO message object. See Sample 24 for the EnumFieldsCollection function.

Sample 23 Examining message content 

   1:' VBScript function enumerating message content from a CDOEX object.    ' 
   .: 
   6:' This function needs an inclusion in the parent of the following:      ' 
   7:'                                                                       ' 
   8:' Functions:                                                             ' 
   9:'                                                                        ' 
  10:'     EnumFieldsCollectionFunction.vbs                                      ' 
  11:'                                                                        ' 
  12:' Constants:                                                             ' 
  13:'                                                                        ' 
  14:'     cEnumFieldsCollection                                                 ' 
  ..: 
  20:' ----------------------------------------------------------------------- 
  21:Function EnumMessageContent (strURL) 
  ..: 
  ..: 
  26:     Set objMessage = CreateObject ("CDO.Message") 
  27:     objMessage.DataSource.Open strURL 
  ..: 
  ..: 
  34:   DisplayText "AutoGenerateTextBody(CDOEX)", _ 
                    objMessage.AutoGenerateTextBody 
  35:   DisplayText "BCC(CDOEX)",                      objMessage.BCC 
  36:   DisplayText "CC(CDOEX)",                       objMessage.CC 
  37:   DisplayText "DSNOptions(CDOEX)",               objMessage.DSNOptions 
  38:   DisplayText "FollowUpTo(CDOEX)",               objMessage.FollowUpTo 
  39:   DisplayText "From(CDOEX)",                     objMessage.From 
  40:   DisplayText "HTMLBody(CDOEX)",                 objMessage.HTMLBody 
  41:   DisplayText "Keywords(CDOEX)",                 objMessage.Keywords 
  42:   DisplayText "MDNRequested(CDOEX)",             objMessage.MDNRequested 
  43:   DisplayText "MIMEFormatted(CDOEX)",            objMessage.MIMEFormatted 
  44:   DisplayText "Newsgroups(CDOEX)",               objMessage.Newsgroups 
  45:   DisplayText "Organization(CDOEX)",             objMessage.Organization 
  46:   DisplayText "ReceivedTime(CDOEX)",             objMessage.ReceivedTime 
  47:   DisplayText "ReplyTo(CDOEX)",                  objMessage.ReplyTo 
  48:   DisplayText "Sender(CDOEX)",                   objMessage.Sender 
  49:   DisplayText "SentOn(CDOEX)",                   objMessage.SentOn 
  50:   DisplayText "Subject(CDOEX)",                  objMessage.Subject 
  51:   DisplayText "TextBody(CDOEX)",                 objMessage.TextBody 
  52:   DisplayText "To(CDOEX)",                       objMessage.To 
  ..: 
  ..: 
  57:   If cEnumFieldsCollection Then EnumFieldsCollection objMessage.Fields 
  ..: 
  ..: 
  62:      EnumFieldsCollection objMessage.Configuration.Fields 
  ..: 
  ..: 
  65:    ' At this level of the object hierarchy, it is possible to send,  
  66:    ' reply, forward, add an attachment, and so on, to a message. 
  67:    ' objMessage.AddAttachment      
  68:    ' objMessage.AddRelatedBodyPart      
  69:    ' objMessage.BodyPart 
  70:    ' objMessage.CreateMHTMLBody      
  71:    ' objMessage.Forward      
  72:    ' objMessage.Post      
  73:    ' objMessage.PostReply      
  74:    ' objMessage.Reply      
  75:    ' objMessage.ReplyAll      
  76:    ' objMessage.Send      
  ..: 
  ..: 
  83:End Function

Line 62 retrieves the associated Fields* *collection through the IConfiguration interface. This interface contains the communication parameters for the message, including the SMTP Server name, the SMTP Server port, the Authentication type, and so on. For more information about the IConfiguration interface, see the Microsoft Platform SDK at https://msdn.microsoft.com/library.

The message object also contains methods to forward, reply to, post, and send messages (lines 67 to 76).

Management Point 14 The message object 

At this stage of the script:

  • You know the configuration of a message in the Exchange store.

  • You can send, reply to, and forward messages.

See points 11, 18, L, and S in Figure 3 to locate this operation in the Exchange 2000 logical view.

Going Deeper into the Exchange 2000 Objects Hierarchy

The script in Sample 3 examines many, but not all, aspects of the Exchange 2000 CDOEX COM objects hierarchy. For example, the script does not explore the calendaring, contacts, journal, and notes folders, or their associated objects. The script also stops at the message object level. You can, however, go deeper by looking at the message body parts. If the message is a Multipurpose Internet Mail Extensions (MIME) message (Sample 23, line 43), you can use the IBodyPart interface (line 69) to decompose it.

The purpose of EnumAllInXL.wsf is to further explore the Exchange 2000 COM objects hierarchy in Figure 3. The script shows which COM technologies to use to manage specific components. You can retrieve additional information by adding the following items to previous sample scripts.

  • A SelectCase statement at line 59 of Sample 21. This allows to you to explore the content of objects of different types.

  • An IbodyPart interface exploration process, starting with the message object in Sample 23.

Output Result

Cc750307.ex2kw05(en-us,TechNet.10).gif

Figure 5 Exchange 2000 Exchange System Manager window

While EnumAllInXL.wsf runs and loads information into an Excel spreadsheet, the script displays the different steps executed during the exploration. For example, in Figure 6, the script shows that Exchange 2000 server information is retrieved from WMI (line 6) and CDOEXM (line 7).

In this example, the script runs on a computer named DC01-CPQ. The Exchange 2000 configuration used for the sample output in Figure 6 has four storage groups.

The First Storage Group (line 9) contains:

  • Mailbox Store A (line 11).

  • Mailbox Store B (line 81).

  • Public Folder Store A (line 87).

  • Hierarchy Name: 'Public Folders'

  • Public Folder Store E (line 119).

  • Hierarchy Name: 'Public Folders Tree (E)'

The Second Storage Group (line 125) contains:

  • Mailbox Store C (line 127).

  • Public Folder Store C (line 133).

  • Hierarchy Name: 'Public Folders Tree (C)'

The Third Storage Group (line 139).

  • Mailbox Store D (line 141).

  • Public Folder Store D (line 147).

  • Hierarchy Name: 'Public Folders Tree (D)'

The Fourth Storage Group (line 153).

  • Mailbox Store E (line 155).

  • Public Folder Store B (line 161).

  • Hierarchy Name: 'Public Folders Tree (B)'

DC01-CPQ (lines 6 and 7) is one of two servers in an Exchange 2000 organization. DC02-CPQ (lines 126 and 127) is the other.

Lines 19 to 53 examine the mailbox of the user who is currently logged on and running the script. The messages in the mailbox have their properties loaded into an Excel spreadsheet (lines 36 to 41 and lines 48 to 53).

Figure 6 The EnumE2KInXL sample output

 1:Microsoft Windows Script Host Version 5.1 for Windows 
 2:Copyright Microsoft Corporation. All rights reserved. 
 3: 
 4:Found Exchange Organization called 'First Organization'. 
 5: 
 6:Reading Exchange Server 'DC01-CPQ' information via 'WMI'. 
 7:Reading Exchange server 'DC01-CPQ' information via 'CDOEXM.ExchangeServer'. 
 8:ADO Field count=37 
 9:Reading Storage Group 'First Storage Group' information via  
    'CDOEXM.StorageGroup'. 
 10:ADO Field count=33 
 11:Reading Mailbox Store DB 'Mailbox Store A' information via  
    'CDOEXM.MailBoxStoreDB'. 
 12:ADO Field count=39 
 13:Retrieving Person/mailbox list via 'ADSI/ADO' LDAP Query. 
 14:Retrieving Person information 'SystemMailbox{70F...}' via 'CDO.Person' 
 15:ADO Field count=51 
 16:Retrieving mailbox information 'SystemMailbox{70F...}' via 
    'IMailboxStore CDOEXM' interface. 
 17:Retrieving Person information 'LISSOIR Alain' via 'CDO.Person' 
 18:ADO Field count=55 
 19:Retrieving mailbox information 'LISSOIR Alain' via  
    'IMailboxStore CDOEXM' interface. 
 20:Browsing mailbox 'LISSOIR Alain'. 
 21:Browsing 'file://./backofficestorage/Example.com/MBX/Alain.Lissoir' 
    via 'ADO'. 
 22:urn:content-classes:taskfolder='Tasks' 
 23:ADO Field count=72 
 24:urn:content-classes:notefolder='Notes' 
 25:ADO Field count=72 
 26:urn:content-classes:journalfolder='Journal' 
 27:ADO Field count=72 
 28:urn:content-classes:mailfolder='Drafts' 
 29:ADO Field count=157 
 30:urn:content-classes:contactfolder='Contacts' 
 31:ADO Field count=188 
 32:urn:content-classes:calendarfolder='Calendar' 
 33:ADO Field count=116 
 34:urn:content-classes:folder='Sent Items' 
 35:ADO Field count=72 
 36:urn:content-classes:message='TEST.EML' 
 37:ADO Field count=156 
 38:Examining message content via 'CDOEX'. 
 39:ADO Field count=67 
 40:Examining message Configuration via 'CDOEX'. 
 41:ADO Field count=15 
 42:urn:content-classes:folder='Deleted Items' 
 43:ADO Field count=72 
 44:urn:content-classes:folder='Outbox' 
 45:ADO Field count=72 
 46:urn:content-classes:mailfolder='Inbox' 
 47:ADO Field count=157 
 48:urn:content-classes:message='TEST.EML' 
 49:ADO Field count=157 
 50:Examining message content via 'CDOEX'. 
 51:ADO Field count=67 
 52:Examining message Configuration via 'CDOEX'. 
 53:ADO Field count=15 
 54:Retrieving Person information 'HIGHTOWER Kim' via 'CDO.Person' 
 55:ADO Field count=70 
 56:Retrieving mailbox information 'HIGHTOWER Kim' via  
    'IMailboxStore  CDOEXM' interface. 
 57:Retrieving Person information 'MITCHELL Linda' via 'CDO.Person' 
 58:ADO Field count=70 
 59:Retrieving mailbox information 'MITCHELL Linda' via  
    'IMailboxStore  CDOEXM' interface. 
 60:Retrieving Person information 'COOPER Scott' via 'CDO.Person' 
 61:ADO Field count=69 
 62:Retrieving mailbox information 'COOPER Scott' via  
    'IMailboxStore  CDOEXM' interface. 
 63:Retrieving Person information 'MUGHAL Salman' via 'CDO.Person' 
 64:ADO Field count=70 
 65:Retrieving mailbox information 'MUGHAL Salman' via  
    'IMailboxStore  CDOEXM' interface. 
 66:Retrieving Person information 'PHUA Meng' via 'CDO.Person' 
 67:ADO Field count=70 
 68:Retrieving mailbox information 'PHUA Meng' via  
    'IMailboxStore  CDOEXM' interface. 
 69:Retrieving Person information 'WOOD John' via 'CDO.Person' 
 70:ADO Field count=70 
 71:Retrieving mailbox information 'WOOD John' via  
    'IMailboxStore  CDOEXM' interface. 
 72:Retrieving Person information 'SCHNEIDER Detlef' via 'CDO.Person' 
 73:ADO Field count=70 
 74:Retrieving mailbox information 'SCHNEIDER Detlef' via  
    'IMailboxStore CDOEXM' interface. 
 75:Retrieving Person information 'SEIDL Birgit' via 'CDO.Person' 
 76:ADO Field count=69 
 77:Retrieving mailbox information 'SEIDL Birgit' via  
   'IMailboxStore CDOEXM' interface. 
 78:Retrieving Person information 'WEST Paul' via 'CDO.Person' 
 79:ADO Field count=70 
 80:Retrieving mailbox information 'WEST Paul' via  
    'IMailboxStore CDOEXM' interface. 
 81:Reading Mailbox Store DB 'Mailbox Store B' information via 
      'CDOEXM.MailBoxStoreDB'. 
 82:ADO Field count=39 
 83:Retrieving Person/mailbox list via 'ADSI/ADO' LDAP Query.   
 84:Retrieving Person information 'SystemMailbox{6AA...}' via 'CDO.Person' 
 85:ADO Field count=51 
 86:Retrieving mailbox information 'SystemMailbox{6AA...}' via  
    'IMailboxStore CDOEXM' interface. 
 87:Reading Public Store DB 'Public Folder Store A' information via  
   'CDOEXM.PublicStoreDB'. 
 88:ADO Field count=48 
 89:Retrieving FolderTree 'Public Folders' information via 'CDOEXM'. 
 90:ADO Field count=21 
 91:Browsing 'Public Folders' tree. 
 92:Browsing 'file://./backofficestorage/Example.com/Public Folders'  
    via 'ADO'. 
 93:urn:content-classes:folder='MyFolder' 
 94:ADO Field count=72 
 95:urn:content-classes:mailfolder='Services' 
 96:ADO Field count=157 
 97:urn:content-classes:mailfolder='Professional Services' 
 98:ADO Field count=157 
 99:urn:content-classes:message='Part 2 - The combination of WSH-ADSI  
     under Windows 2000.EML' 
100:ADO Field count=157 
101:Examining message content via 'CDOEX'. 
102:ADO Field count=65 
103:Examining message Configuration via 'CDOEX'. 
104:ADO Field count=15 
105:urn:content-classes:message='Part 1 - Understanding WSH and ADSI  
    in Windows 2000.EML'  
106:ADO Field count=157 
107:Examining message content via 'CDOEX'. 
108:ADO Field count=65 
109:Examining message Configuration via 'CDOEX'. 
110:ADO Field count=15 
111:urn:content-classes:mailfolder='Customer Services' 
112:ADO Field count=157 
113:urn:content-classes:mailfolder='Marketing' 
114:ADO Field count=157 
115:urn:content-classes:mailfolder='Corporate Templates' 
116:ADO Field count=157 
117:urn:content-classes:folder='Internet Newsgroups' 
118:ADO Field count=72 
119:Reading Public Store DB 'Public Folder Store E' information via  
    'CDOEXM.PublicStoreDB'. 
120:ADO Field count=44 
121:Retrieving FolderTree 'Public Folders Tree (E)' information via 'CDOEXM'. 
122:ADO Field count=19 
123:Browsing 'Public Folders Tree (E)' tree. 
124:Browsing 'file://./backofficestorage/Example.com/Public Folders  
     Tree (E)' via 'ADO'. 
125:Reading Storage Group 'Second Storage Group' information via  
    'CDOEXM.StorageGroup'. 
126:ADO Field count=32 
127:Reading Mailbox Store DB 'Mailbox Store C' information via  
    'CDOEXM.MailBoxStoreDB'. 
128:ADO Field count=39 
129:Retrieving Person/mailbox list via 'ADSI/ADO' LDAP Query. 
130:Retrieving Person information 'SystemMailbox{A99...}' via 'CDO.Person' 
131:ADO Field count=51 
132:Retrieving mailbox information 'SystemMailbox{A99...}' via  
    'IMailboxStore  CDOEXM' interface. 
133:Reading Public Store DB 'Public Folder Store C' information via  
    'CDOEXM.PublicStoreDB'. 
134:ADO Field count=44 
135:Retrieving FolderTree 'Public Folders Tree (C)' information via 'CDOEXM'. 
136:ADO Field count=19 
137:Browsing 'Public Folders Tree (C)' tree. 
138:Browsing 'file://./backofficestorage/Example.com/Public Folders  
     Tree (C)' via 'ADO'. 
139:Reading Storage Group 'Third Storage Group' information via  
    'CDOEXM.StorageGroup'. 
140:ADO Field count=32 
141:Reading Mailbox Store DB 'Mailbox Store D' information via  
    'CDOEXM.MailBoxStoreDB'. 
142:ADO Field count=39 
143:Retrieving Person/mailbox list via 'ADSI/ADO' LDAP Query. 
144:Retrieving Person information 'SystemMailbox{CE2...}' via 'CDO.Person' 
145:ADO Field count=51 
146:Retrieving mailbox information 'SystemMailbox{CE2...}' via  
    'IMailboxStore CDOEXM' interface. 
147:Reading Public Store DB 'Public Folder Store D' information  
    via'CDOEXM.PublicStoreDB'. 
148:ADO Field count=44 
149:Retrieving FolderTree 'Public Folders Tree (D)' information via 'CDOEXM'. 
150:ADO Field count=19 
151:Browsing 'Public Folders Tree (D)' tree. 
152:Browsing 'file://./backofficestorage/Example.com/Public Folders  
     Tree (D)' via 'ADO'. 
153:Reading Storage Group 'Fourth Storage Group' information via  
    'CDOEXM.StorageGroup'. 
154:ADO Field count=32 
155:Reading Mailbox Store DB 'Mailbox Store E' information via  
    'CDOEXM.MailBoxStoreDB'. 
156:ADO Field count=39 
157:Retrieving Person/mailbox list via 'ADSI/ADO' LDAP Query. 
158:Retrieving Person information 'SystemMailbox{CBD...}' via 'CDO.Person' 
159:ADO Field count=51 
160:Retrieving mailbox information 'SystemMailbox{CBD...}' via  
    'IMailboxStore CDOEXM' interface. 
161:Reading Public Store DB 'Public Folder Store B' information via  
    'CDOEXM.PublicStoreDB'. 
162:ADO Field count=44 
163:Retrieving FolderTree 'Public Folders Tree (B)' information via 'CDOEXM'. 
164:ADO Field count=19 
165:Browsing 'Public Folders Tree (B)' tree. 
166:Browsing 'file://./backofficestorage/Example.com/Public Folders  
     Tree (B)' via 'ADO'. 
167:Reading Exchange Server 'DC02-CPQ' information via 'WMI'. 
168:Reading Exchange server 'DC02-CPQ' information via 'CDOEXM.ExchangeServer'. 
169:ADO Field count=37
Formatting the Collected Data

The EnumFieldCollection function helps format the retrieved data by enumerating a Fields collection that is passed as a parameter. The only restriction applied by this function is that it does not format all the elements of arrays. When a retrieved property is an array, the function assigns a string <array> to the value loaded in Excel. This is simply to minimize the amount of data to format.

Sample 24 Enumerating the Fields collection and formatting the collected data 

   1:' VBScript function enumerating the Fields collection from a CDO object.  
   2:' If the value is printable, the content is displayed; otherwise the      
   3:' variant value type is displayed.                                        
   4:'                                                                         
   5:' This function uses a DisplayText function defined in the caller module. 
  ..: 
  12:Option Explicit 
  13: 
  14:' ----------------------------------------------------------------------- 
  15:Function EnumFieldsCollection (objFields) 
  ..: 
  ..: 
  25:        For Each objField In objFields 
  26: 
  27:            If IsArray (objFields (objField.Name)) Then 
  ..: 
  33:               DisplayField objField.Name, "<Array>" 
  ..: 
  36:            Else 
  37:               DisplayField objField.Name, objFields (objField.Name) 
  38:            End If 
  39: 
  40:        Next 
  ..: 
  44:End Function 
  45: 
  46:' ----------------------------------------------------------------------- 
  47:Function DisplayField (strName, varValue) 
  48: 
  49:Dim strToDisplay 
  50: 
  51:        Select Case VarType (varValue) 
  52:               Case vbInteger, _ 
  53:                    vbLong, _ 
  54:                    vbSingle, _ 
  55:                    vbDouble, _ 
  56:                    vbCurrency, _ 
  57:                    vbDate, _ 
  58:                    vbString, _ 
  59:                    vbBoolean 
  60:                    strToDisplay = varValue 
  61: 
  62:               ' Display the type instead of the value when the returned 
                    ' type prevents it from being displayed directly.  
  63:               Case vbEmpty 
  64:                    strToDisplay = "(vbEmpty)" 
  65:               Case vbNull 
  66:                    strToDisplay = "(vbNull)" 
  67:               Case vbObject 
  68:                    strToDisplay = "(vbObject)" 
  69:               Case vbError 
  70:                    strToDisplay = "(vbError)" 
  71:               Case vbVariant 
  72:                    strToDisplay = "(vbVariant)" 
  73:               Case vbDataObject 
  74:                    strToDisplay = "(vbDataObject)" 
  75:               Case vbByte 
  76:                    strToDisplay = "(vbByte)" 
  77:               Case vbArray 
  78:                    strToDisplay = "(vbArray)" 
  79: 
  80:               Case Else 
  81:                    strToDisplay = "(VariantType:" & _ 
                                        VarType (varValue) & ")" 
  82: 
  83:        End Select 
  84: 
  85:        DisplayText strName & "(ADO Field)", strToDisplay 
  86: 
  87:End Function

Advanced Script Samples

The remainder of this white paper uses the COM technologies explored in previous sections to build two advanced scripts. You can use these scripts as they are or as the basis for your own management script for Exchange 2000.

Moving Exchange 2000 Mailboxes Based on an LDAP Query

Sample 11 and Sample 13 demonstrate how to use the IMailboxStore interface aggregated to an ADSI.User object or to a CDO.Person* object.* The same scripts show the MoveMailbox method associated with these interfaces.

Sample 10 and Sample 12 demonstrate the ADSearchFunction, which retrieves mailboxes and their associated object properties from ADSI and CDOEX.

You can combine the ADSearchFunction with the MoveMailbox method to write a script that moves mailboxes between mailbox stores, storage groups, and Exchange 2000 servers, based on the results of an LDAP query. For example, if you want to move all users in a department to one server or to a particular Exchange store, you can run an LDAP query in Active Directory and move the users found by this query to your chosen location. This is the purpose of the next sample.

Sample 25 uses ADSI to access a mailbox. First, the script retrieves the current computer name (lines 89 to 92). From lines 115 to 120, the script retrieves the required parameters to perform a mailbox move operation.

The first parameter is the LDAP Query string (line 115). The ADSearch function uses this string as a filter to perform the query (lines 152 to 156), so the query syntax must be LDAP compliant. The query is executed in the default Active Directory context (lines 126 and 153), which corresponds to the domain membership of the computer that runs the script. Therefore, only the user's part of this domain is retrieved.

Next, the distinguished name of the target mailbox store is constructed (lines 131 to 139), based on the parameters in the command line.

  • Line 131 specifies the mailbox store name retrieved from line 116.

  • Line 132 specifies the storage group of the given mailbox store, which is retrieved from line 117.

  • Line 134 specifies the server name of the given storage group, which is retrieved from line 118.

  • Line 135 specifies the administration group name of the given server name, which is retrieved from line 119.

  • Line 137 specifies the organization name of the given administration group, which is retrieved from the GetExchangeOrg function call (line 120). For more information about the GetExchangeOrg function, see Sample 50.

  • Line 139 specifies the root domain of the tree retrieved at line 127. This completes the mailbox store distinguished name. In this case, only the root domain name can be used because the Active Directory Configuration Naming Context is always lower than the root domain name of the tree in the directory structure.

Sample 25 Moving mailboxes from an ADSI.User object based on a LDAP query 

   1:<!-- VBScript script making LDAP searches in Active Directory on a    --> 
   2:<!-- user objectClass. Matching users are moved from their current    --> 
   3:<!-- Exchange store to the given store. The script uses the ADSI User --> 
   4:<!-- object that is attached to the mailbox for the move operation.   --> 
  ..: 
  45:<job> 
  46:     <script language="VBScript" src="ADSearchFunction1.vbs" /> 
  47:     <script language="VBScript" src="GetMSExchangeOrgFunction.vbs" /> 
  48:     <script language="VBScript" src="TinyErrorHandler.vbs" /> 
  49: 
  50:     <script language="VBScript"> 
  51: 
  52:     Option Explicit 
  ..: 
  85:     ' ------------------------------------------------------------------- 
  86:     On Error Resume Next 
  ..: 
  89:     Set WNetwork = Wscript.CreateObject("Wscript.Network") 
  90:     strComputerName = WNetwork.ComputerName 
  91:     Wscript.DisconnectObject (WNetwork) 
  92:     Set WNetwork = Nothing 
  93: 
  94:     Set objArguments = Wscript.Arguments 
  95: 
  96:     If objArguments.Count <> 5 Then 
  97:        WScript.Echo "Usage:" 
  98:        Wscript.Echo " QueryAndMoveMBTo " & _ 
  99:                     chr(34) & "(LDAPQueryFilter)" & chr(34) & " " & _ 
 100:                     chr(34) & "TargetStore" & chr(34) & " " & _ 
 101:                     chr(34) & "TargetStorageGroup" & chr(34) & " " & _ 
 102:                     chr(34) & "TargetServer" & chr(34) & " " & _ 
 103:                     chr(34) & "TargetAdministrativeGroup" & chr(34) 
 104:        Wscript.Echo 
 105:        Wscript.Echo "Sample:" 
 106:        Wscript.Echo " QueryAndMoveMBTo " & _ 
 107:                     chr(34) & "(givenName=J*)" & chr(34) & " " & _ 
 108:                     chr(34) & "Mailbox Store B" & chr(34) & " " & _ 
 109:                     chr(34) & "First Storage Group" & chr(34) & " " & _ 
 110:                     chr(34) & strComputerName & chr(34) & " " & _ 
 111:                     chr(34) & "First Administrative Group" & chr(34) 
 112:        WScript.Quit (1) 
 113:     End If 
 114: 
 115:     strLDAPQueryFilter = objArguments (0) 
 116:     strTargetStore = objArguments (1) 
 117:     strTargetStorageGroup = objArguments (2) 
 118:     strTargetServerName = objArguments (3) 
 119:     strTargetAdministrativeGroup = objArguments (4) 
 120:     strOrganization = GetExchangeOrg () 
 121: 
 122:     ' ------------------------------------------------------------------- 
 123:     ' Get the default Windows 2000 domain name. 
 124:     Wscript.Echo "Binding to RootDSE to get default Domain Name." 
 125:     Set objRoot = GetObject("LDAP://RootDSE") 
 126:     strDefaultDomainNC = objRoot.Get("DefaultNamingContext") 
 127:     strRootDomainNC = objRoot.Get("RootDomainNamingContext") 
 128:     Set objRoot = Nothing 
 129: 
 130:     ' Determine whether the target store exists. 
 131:     strTargetHomeMDB_DN = "cn=" & strTargetStore & "," & _ 
 132:               "cn=" & strTargetStorageGroup & "," & _ 
 133:               "cn=InformationStore," & _ 
 134:               "cn=" & strTargetServerName & ",cn=Servers," & _ 
 135:               "cn=" & strTargetAdministrativeGroup & _ 
 136:                       ",cn=Administrative Groups," & _ 
 137:               "cn=" & strOrganization & "," & _ 
 138:               "cn=Microsoft Exchange,cn=Services,cn=Configuration," & _ 
 139:               strRootDomainNC 
 140: 
 141:     Set objTargetHomeMDB = CreateObject("CDOEXM.MailBoxStoreDB") 
 142:     objTargetHomeMDB.DataSource.Open (strTargetHomeMDB_DN) 
 143:     If Err.Number Then ErrorHandler (Err) 
 144: 
 145:     If objTargetHomeMDB.Status Then 
 146:        WScript.Echo "Target Store '" & objTargetHomeMDB.Name & _ 
                          "' is not mounted." 
 147:        WScript.Quit (1) 
 148:     End If 
 149: 
 150:     ' ------------------------------------------------------------------- 
 151:     ' Search for the list of user mailboxes. 
 152:     Set objResultList = ADSearch ("LDAP://" & strDefaultDomainNC, _ 
 153:                                   strLDAPQueryFilter, _ 
 154:                                   "ADsPath, homeMDB", _ 
 155:                                   "subTree", _ 
 156:                                   False) 
 157:     WScript.Echo 
 158:     WScript.Echo "Number of matches for the LDAP query is " & _ 
 159:                  objResultList.Item ("RecordCount") 
 160:     WScript.Echo 
 161: 
 162:     ' ------------------------------------------------------------------- 
 163:     objResult = objResultList.Items 
 164: 
 165:     ' Two elements are returned from the LDAP query: "AdsPath; homeMDB". 
 166:     ' The first element in the list (intIndice=1) contains the number of 
 167:     ' records in the list, so it is skipped. 
 168:     ' Odd elements contain "ADsPath" and even elements contain "homeMDB". 
 169: 
 170:     For intIndice = 1 to (objResultList.Count - 1) Step 2 
 171:         strUserADsPath = objResult (intIndice) 
 172:         strSourceHomeMDB_DN = objResult (intIndice + 1) 
 173: 
 174:            Set objUser = GetObject(strUserADsPath) 
 175: 
 176:            If LCase(strSourceHomeMDB_DN) = _ 
                    LCase(strTargetHomeMDB_DN) Then 
 177:               WScript.Echo "Skipping user '" & objUser.DisplayName & _ 
 178:                      "'. Actual Store and Target Store are the same." 
 179:            ElseIf _ 
 180:               Isnull (strSourceHomeMDB_DN) Then 
 181:               WScript.Echo "Skipping user '" & objUser.DisplayName & _ 
 182:                      "'. This user does not have a mailbox." 
 183:            Else 
 184:               Set objSourceHomeMDB = _ 
                        CreateObject("CDOEXM.MailBoxStoreDB") 
 185:               objSourceHomeMDB.DataSource.Open (strSourceHomeMDB_DN) 
 186:               If Err.Number Then ErrorHandler (Err) 
 187: 
 188:               If objSourceHomeMDB.Status Then 
 189:                  WScript.Echo "Source Store '" & _ 
                                               objSourceHomeMDB.Name & _ 
 190:                               "' is not mounted." 
 191:                  WScript.Quit (1) 
 192:               End If 
 193: 
 194:               WScript.Echo "Moving user '" & objUser.DisplayName & _ 
                                 "' from:" 
 195:               Wscript.Echo " Store '" & objSourceHomeMDB.Name & _ 
 196:                            "' to Store '" & objTargetHomeMDB.Name & "'" 
 197: 
 198:               ' Moving the mailbox. 
 199:               objUser.MoveMailbox "LDAP://" & strTargetHomeMDB_DN 
 200:               If Err.Number Then ErrorHandler (Err) 
 201: 
 202:               WScript.DisconnectObject objSourceHomeMDB 
 203:               Set objSourceHomeMDB = Nothing 
 204:           End If 
 205: 
 206:           Set objUser = Nothing 
 207:     Next 
 ...: 
 212:     </script> 
 213:</job>

Line 141 instantiates an object to get information about the target store specified in the command line. The object verifies whether the given store exists (lines 142 and 143) and if it is mounted (line 145). Then the LDAP query is executed (lines 152 to 156). The query retrieves two important pieces of information:

  • The ADsPath of the user object to bind to (line 174). This allows the aggregated IMailboxStore interface to move the mailbox (line 199). Less importantly, it displays the mailbox name during the move operation (lines 177, 181, and 194).

  • The homeMDB property. This property verifies that the user's current mailbox store is not the same as the target store (line 176), and that the user is mailbox-enabled. If not, the homeMDB property is not set in the directory (line 180). The homeMDB property also verifies that the mailbox store is mounted (line 188).

After all the checks are completed successfully, the mailbox is moved (line 199).

Note You can also verify that the user is mailbox-enabled with the LDAP query. To do so you must combine the user query from the command line with the supplemental condition (homeMDB=*). This requires some string manipulations in the script. The test is made here and not in the LDAP query for simplicity only.

The script then processes the next user in the list, if any (line 207).

Until line 170, Sample 26 uses exactly the same code as Sample 25. Line 174 uses a CDO.Person object instead of an ADSI.User object. Line 175 uses the GetInterface method to aggregate the IMailboxStore interface to the CDO.Person object. Except for these changes and their associated variable declarations, both scripts use exactly the same logic. Sample 26 displays the adaptation made from line 170 to use the CDO.Person object.

Sample 26 Moving mailboxes using a CDO.Person object based on an LDAP query 

 ...: 
 ...: 
 170:     For intIndice = 1 to (objResultList.Count - 1) Step 2 
 171:         strUserADsPath = objResult (intIndice) 
 172:         strSourceHomeMDB_DN = objResult (intIndice + 1) 
 173: 
 174:         Set objPerson = CreateObject ("CDO.Person") 
 175:         Set objMailbox = objPerson.GetInterface ("IMailboxStore ") 
 176: 
 177:         objPerson.DataSource.Open (strUserADsPath) 
 178: 
 179:         If LCase(strSourceHomeMDB_DN) = LCase(strTargetHomeMDB_DN) Then 
 180:            WScript.Echo "Skipping user '" & _ 
                      objPerson.Fields("displayName") & _ 
 181:                 "'. Actual Store and Target Store are the same." 
 182:         ElseIf _ 
 183:            Isnull (strSourceHomeMDB_DN) Then 
 184:            WScript.Echo "Skipping user '" & _ 
                      objPerson.Fields("displayName") & _ 
 185:                 "'. This user does not have a mailbox." 
 186:         Else 
 187:            Set objSourceHomeMDB = CreateObject("CDOEXM.MailBoxStoreDB") 
 188:            objSourceHomeMDB.DataSource.Open (strSourceHomeMDB_DN) 
 189:            If Err.Number Then ErrorHandler (Err) 
 190: 
 191:            If objSourceHomeMDB.Status Then 
 192:               WScript.Echo "Source Store '" & objSourceHomeMDB.Name & _ 
 193:                            "' is not mounted." 
 194:               WScript.Quit (1) 
 195:            End If 
 196: 
 197:            WScript.Echo "Moving user '" & _ 
                              objPerson.Fields("displayName") & "'from:" 
 198:            Wscript.Echo " Store '" & objSourceHomeMDB.Name & _ 
 199:                         "' to Store '" & objTargetHomeMDB.Name & "'" 
 200: 
 201:            ' Moving the mailbox. 
 202:            objMailbox.MoveMailbox "LDAP://" & strTargetHomeMDB_DN 
 203:            If Err.Number Then ErrorHandler (Err) 
 204: 
 205:            objPerson.DataSource.Save 
 206: 
 207:            WScript.DisconnectObject objSourceHomeMDB 
 208:            Set objSourceHomeMDB = Nothing 
 209:         End If 
 210: 
 211:         Set objMailBox = Nothing 
 212: 
 213:         WScript.DisconnectObject objPerson 
 214:         Set objPerson = Nothing 
 215:  Next 
 216: 
 217:  WScript.DisconnectObject objTargetHomeMDB  
 218:  Set objTargetHomeMDB = Nothing 
 219: 
 220:  </script> 
 221:</job>

The following are two command line samples to move mailboxes.

First, to move all the users with a first name starting with "J" to a mailbox store named "Mailbox Store B" in "First Storage Group", located on "MyServer" in "First Administration Group", use the following:

QueryAndMoveMBto (ADSI).vbs" "(givenName=J*)" 
                             "Mailbox Store B" "First Storage Group" 
                             "MyServer" 
                             "First Administrative Group"                       

Note Each parameter has been placed on a different line for clarity. From the command prompt, these parameters must be typed on the same line, separated by a space.

Second, if you want to move all the users from a specific mailbox store to another location, you must use a more complex LDAP query. This query is based on the distinguished name of the mailbox store housing the users. The SystemMailbox should not be moved. This is why there is a "(!(cn=Sys*))" statement in the query. Other parameters are the same as the prior sample.

Note At first glance, you might think the query can be made with the objectCategory property. However, SystemMailbox is disabled as an objectCategory user in Active Directory. So a query based on this category does not exclude the SystemMailbox from the list of mailboxes. Attempts to move a list of mailboxes that contains the SystemMailbox will not be performed properly.

In this sample, only the LDAP query is different:

"QueryAndMoveMBto (ADSI).vbs" "(&                                       
                                (!(cn=sys*))                            
                                (objectCategory=user)                   
                                (homeMDB=CN=Mailbox Store A,            
                                         CN=First Storage Group,        
                                         CN=InformationStore,           
                                         CN=MyOtherExchangeServer,         
                                         CN=Servers,                    
                                         CN=First Administrative Group, 
                                         CN=Administrative Groups,      
                                         CN=First Organization,         
                                         CN=Microsoft Exchange,         
                                         CN=Services,                   
                                         CN=Configuration,              
                                         DC=Example,            
                                         DC=com)                        
                               )"                                       
                               "Mailbox Store B"                        
                               "First Storage Group"                    
                               "MyServer"                     
                               "First Administrative Group"            

Monitoring Exchange 2000 Server Activity

The server activity monitor for Microsoft® Exchange 2000 Server is based on the WMI infrastructure (see "An Overview of Exchange 2000 WMI Providers" in the Appendix for more information about WMI and Exchange 2000). The monitoring feature included in the Exchange System Manager provides only the status of the component monitored. The main reason for this is that the routing table carries the information. The routing table does not have enough space to hold all of the information provided by WMI. It only carries the status.

The second advanced script, E2KWatch, retrieves the complete set of information available from WMI and sends it by e-mail (see Figure 8). The script makes extensive use of the WMI infrastructure of Windows 2000 and Exchange 2000. The monitoring is based on WMI asynchronous events.

Sample 27 is a basic example of a WMI asynchronous event monitoring a Windows 2000 service modification.

Sample 27 A basic WMI asynchronous event handler for the Win32_Service class 

   1:' VBScript script creating an asynchronous event notification and         
   2:' looping. When a change event occurs on a service an event is triggered. 
   .: 
   9:Option Explicit 
  ..: 
  ..: 
  14:Set objSink = WScript.CreateObject ("WbemScripting.SWbemSink","SINK_") 
  15: 
  16:Set objService = _ 
            GetObject("WinMgmts:{impersonationLevel=impersonate, (security)}")  
  17: 
  18:objService.ExecNotificationQueryAsync objSink, _  
  19:       "Select * FROM __InstanceModificationEvent WITHIN 1 Where " & _ 
  20:       "TargetInstance isa 'Win32_Service'" 
  21: 
  22:WScript.Echo "Waiting for events..."  
  23: 
  24:Do 
  25: 
  26:  WScript.Sleep (5000) 
  27: 
  28:Loop 
  29: 
  30:objSink.Cancel 
  31: 
  ..: 
  37:' ------------------------------------------------------------------------ 
  38:Sub SINK_OnObjectReady (objWbemObject, objWbemAsyncContext) 
  39: 
  40:    WScript.Echo FormatDateTime(Date, vbLongDate) & " at " & _ 
  41:                 FormatDateTime(Time, vbShortTime) & ": " & _ 
  42:                 objWbemObject.TargetInstance.DisplayName & " " & _ 
  43:                 objWbemObject.TargetInstance.State & _ 
  44:                 " (" & objWbemObject.TargetInstance.Name & "). " & _ 
  45:          "Startup mode is '" & objWbemObject.TargetInstance.StartMode & "'." 
  46: 
  47:End Sub

Every change made to a Windows 2000 service (startup mode, logon credentials, stop, start, and so on) generates a WMI event to the SINK_OnObjectReady function. The two parameters provided by this function are objects initialized by the WMI infrastructure:

  • bjWbemObject*:* This object represents the component (Windows 2000 Services, logical disks, CPU, event log, and so on) related to the event.

  • bjWbemAsyncContext: This object helps determine the context of the WMI event. You can specify a context (which is a string or a value parameter) during the initialization of the WMI asynchronous event. This parameter is then passed back during the event through objWbemAsyncContext.

The E2KWatch script uses exactly the same structure and the same logic as Sample 27. This sample can be divided into two important sections:

  • WMI initialization (lines 14 to 20)

  • WMI event handling (lines 38 to 47)

The E2KWatch script applies this logic to many different WMI classes.

Note The next sections require a strong understanding of WMI. For more information about WMI, see the WMI SDK at https://msdn.microsoft.com/library.

The script can monitor many aspects of an Exchange 2000 server. These aspects can be classified in two categories:

  • Windows 2000 and its associated providers

  • Exchange 2000 and its associated providers

Monitoring Windows 2000

With the help of the Windows 2000 standard WMI classes, it is possible to monitor the following:

Windows 2000 services

Any service modification in the system can generate a WMI asynchronous event. The Win32_Service WMI class allows you to monitor the Exchange 2000 server status. If a service stops, the script restarts it and sends an e-mail notification. (See Sample 27.)

Disk space requirements

When running Exchange (like any other server) you must guarantee a minimum of free disk space in the system. This allows space for repairs and integrity check operations in case of a problem. The script uses the Win32_LogicalDisk WMI class to trigger an event when the free disk space threshold is reached.

Process CPU usage

The script uses a new class created by the compilation of a Management Object File (.mof) to access the process counters. If a process exceeds the time allowed, the WMI infrastructure triggers an event.

Processor CPU activity

The script uses the Win32_Processor WMI class to monitor CPU activity. If CPU usage exceeds a certain threshold, the WMI infrastructure triggers an event. This event can be completely different from the Process CPU usage, because two processes can use 40 percent of the CPU time, which is acceptable in some circumstances, but it will result in 80 percent of CPU time usage.

Store size

The script uses the CIM_DATAFile WMI class to monitor the sizes of the .edb and .stm files. If the files reach a certain size, an alert is triggered. If the size reaches a second (higher) limit, the corresponding store will be dismounted with CDOEXM.

NT Event log

The script uses the Win32_NTLogEvent WMI class to detect events created in the event log and trigger alerts captured by the script.

Monitoring Exchange 2000

Server status

The server states are provided by the WMI ExchangeServerState class. The script initiates an asynchronous event for any change in properties.

Connector status between servers

The connector states are provided by the WMI ExchangeConnectorState class. The script will initiate an asynchronous event for any change in properties.

Link and queue increasingTime

The script programs a WMI asynchronous event for any increasingTime value higher than the prior increasingTime value. WMI does not trigger an event for any other link and queue property changes (even if the increasingTime goes to zero). This test acts as a filter. If the increasingTime value reaches a certain threshold, the script sends an alert. The script uses the ExchangeLink WMI class and the ExchangeQueue WMI class.

The E2KWatch.wsf Script

The E2KWatch.wsf script contains more than 2,000 lines of code. This script reproduces the logic in Sample 27. Because of the number of lines, it is not possible to publish and comment the complete script listing. The following sections concentrate on the key parts of the script. The script uses the WMI infrastructure and many Windows 2000 and Exchange 2000 WMI classes.

The Configuration File

Because it has such a large number of parameters, the E2KWatch script uses a configuration file. This file holds all the parameters related to the monitoring of a particular Exchange 2000 Server. The script must run locally on the Exchange server.

The configuration file corresponds to the server DC01-CPQ. The ESM window of this server is shown in Figure 5. To get an idea of the server configuration, see the output file in Figure 6.

The E2KWatch script includes the ReadConfigurationFile function (this document does not enter into the details of this function). Figure 7 shows a sample of the configuration file for the server DC01-CPQ.

Figure 7 The E2KWatch configuration file

   1:# E2KWatch configuration file.    .:    7:    8:# The Internet e-mail address to send the mail alerts to.    9:# (By default, use the currently-logged-on user with the current domain.)   10:  MAILALERTTO=%USERNAME%@%USERDNSDOMAIN%   11:   12:# The Internet e-mail address to send the mail alerts to.   13:# (By default, use the currently-logged-on user with the current domain.)   14:# MAILERRORTO=%USERNAME%@%USERDNSDOMAIN%   15:   16:# Microsoft Exchange System Attendant             17:  Service=MSExchangeSA   18:# Manages Microsoft Exchange Information Storage   19:  Service=MSExchangeIS   20:# Microsoft Exchange POP3   21:  Service=POP3Svc   22:# Microsoft Exchange Routing Engine   23:  Service=RESvc   24:# Microsoft Exchange IMAP4   25:  Service=IMAP4Svc   26:# Microsoft Exchange MTA Stacks   27:  Service=MSExchangeMTA   28:# Microsoft Exchange Event   29:  Service=MSExchangeES   30:# Microsoft Exchange Site Replication Service   31:  Service=MSExchangeSRS   32:# Simple Mail Transport Protocol (SMTP)   33:  Service=SMTPSVC   34:# Microsoft Search   35:  Service=MSSEARCH   36:# SNMP Service   37:  Service=SNMP   38:   39:# Processor #1   40:  Processor=CPU0; 80   41:# Processor #1   42:  Processor=CPU1; 80   43:# Processor #2   44:  Processor=CPU2; 80   45:# Processor #3   46:  Processor=CPU3; 80   47:   48:# Logical Disk   49:  LogicalDisk=C:; 50   50:# Logical Disk   51:  LogicalDisk=D:; 50   52:# Logical Disk   53:  LogicalDisk=E:; 50   54:   55:# Information Store maximum sizes   56:  MDBSTORESIZE=CN=Mailbox Store A,CN=First Storage Group, CN=InformationStore,CN=DC01-CPQ,CN=Servers,CN=First Administrative Group, CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=Example,DC=com; 51200;  81920   57:  STMSTORESIZE=CN=Mailbox Store A,CN=First Storage Group, CN=InformationStore,CN=DC01-CPQ,CN=Servers,CN=First Administrative Group, CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=Example,DC=com; 51200;  81920   ..:   ..:   83:  MDBSTORESIZE=CN=Public Folder Store E,CN=First Storage Group, CN=InformationStore,CN=DC01-CPQ,CN=Servers,CN=First Administrative Group,CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange, CN=Services,CN=Configuration,DC=Example,DC=com; 51200;  81920   84:  STMSTORESIZE=CN=Public Folder Store E,CN=First Storage Group, CN=InformationStore,CN=DC01-CPQ,CN=Servers,CN=First Administrative Group,CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange, CN=Services,CN=Configuration,DC=Example,DC=com; 51200;  81920   85:   86:# EventLog to capture   87:# EventLog=                 88:# EventLog=        MSExchangeSA;9175; MAPI Session;           *;    *   89:# EventLog=        MSExchangeSA;9175; MAPI Session;Application; error   90:  EventLog=        MSExchangeSA;   *;            *;           *;    *   91:  EventLog=        MSExchangeMU;   *;            *;           *;    *   ..:   94:  EventLog=        MSExchangeIS;   *;            *;           *;    *   95:  EventLog=  MSExchangeDSAccess;   *;            *;           *;    *   96:  EventLog=  MSExchangeFBPublic;   *;            *;           *;    *   97:   98:# Exchange System Attendant process   99:  Process=MAD; 50 100:# Exchange Information Store process 101:  Process=STORE; 50 102:# Exchange Message Transfer Agent process 103:  Process=EMSMTA; 50 104:# Any other process using more than 80% of the CPU time 105:# Process=*; 80 106: 107:# Watch the ExchangeRoutingTable WMI Provider. 108:  WMIEXCHANGESERVER=%COMPUTERNAME% 109: 110:# Watch the ExchangeRoutingTable WMI Provider. 111:  WMIEXCHANGECONNECTOR=To First Routing Group 112:  WMIEXCHANGECONNECTOR=To Second Routing Group 113: 114:# Watch the ExchangeQueue WMI Provider. 115:  WMIEXCHANGELINK=*; 30 116: 117:# Watch the ExchangeQueue WMI Provider. 118:  WMIEXCHANGEQUEUE=*; 30 119: 120:# Watch the ExchangeCluster WMI Provider. 121:# WMIEXCHANGECLUSTER=VirtualServerName

This configuration contains the following parameters:

  • Lines 16 to 37 list the Windows 2000 services to be monitored. Note that the service names given are the registry key names of the service, not the display names.

  • Lines 39 to 40 contain the maximum CPU usage for each process in the system.

  • Lines 48 to 53 contain the minimum free disk space percentage.

  • Lines 56 to 84 contain the distinguished name of the store to be monitored. The MDBSTORESIZE keyword refers to the .edb file and the STMSTORESIZE keyword refers to the .stm file. If the store file (.edb or .stm) reaches the size given in the second parameter (after the semi-colon), WMI will trigger an event. If the size is equal to or higher than the third parameter, the script will take action with CDOEXM to dismount the store.

  • Lines 87 to 96 contain the EventLog for which WMI must trigger an alert. If a line has a wildcard, it means "any", and the corresponding value is not taken into consideration to filter the event.

  • Lines 98 to 105 contain the list of processes to be monitored. If the CPU usage is equal to or higher than the specified threshold for a given process, WMI will trigger an event.

Lines 107 to 121 reference the Exchange 2000 WMI providers:

  • Line 108 specifies the Exchange Server name for the ExchangeServerState instance that must change to trigger a WMI event.

  • Lines 111 and 112 specify the Exchange connector name for the ExchangeConnectorState instance that must change to trigger a WMI event.

  • Lines 114 to 118 specify the IncreasingTime threshold that will trigger a WMI event (see "The Exchange 2000 WMI Queue Provider" in the Appendix). Note the wildcard to attach this statement to any link or queue in the system. A link or queue name can also be specified.

The WMI Initialization

Like Sample 27 (lines 14 to 20), E2KWatch.wsf must register the WMI class events based on the parameters in the configuration file. The next sections are a quick review of the WMI asynchronous event initialization for each class used in the E2KWatch.wsf script.

The WMI Win32_Process Class 

The list of services to be monitored is stored in an array initialized by the ReadConfigurationFile function. The script checks the array size (Sample 28, line 360). If it is greater than zero, the array contains at least one service to monitor. All the initialization procedures work the same way.

Lines 361 and 362 create an object to associate the event handler routine label (Win32_ServiceSINK_) to the desired class (Win32_Service).

Lines 363 to 366 execute the WMI registration. There are four things to note in these lines:

  • The data selection statement, "Select *" (line 364). In this case, the script retrieves all the data available from the WMI class.

  • The __InstanceModificationEvent statement, which tells WMI to send a notification whenever a class is modified (line 364).

  • The "WITHIN" statement (line 364) with the cWMI_Within_Win32_Service constant (line 365). This statement specifies the polling interval at which the script requires change notifications for a class. This "WITHIN" statement is mandatory because the Win32_Service WMI class does not have an event provider. The cWMI_Within_Win32_Service constant is defined at the beginning of the script.

  • The condition statement, "TargetInstance isa 'Win32_Service'" (line 366), which specifies the class to be monitored.

Most of the initialization process samples use the same type of statements. The only exception is the Win32_NTLogEvent. See Sample 31 for more information.

The major coding difference between the samples is the condition statement. In Sample 28 (line 366), the statement only concerns the class itself. In this case, any change to an instance of the specified class (Win32_Service) triggers an event.

Sample 28 The WMI Win32_Service class asynchronous event registration 

 ...: 
 ...: 
 359:' Watch the Win32_Service. 
 360:If UBound (strServiceName) Then 
 361:   Set objWin32_ServiceSink = _ 
          WScript.CreateObject("WbemScripting.SWbemSink", "Win32_ServiceSINK_") 
 362:                                                         
 363:   objWMIService.ExecNotificationQueryAsync objWin32_ServiceSink, _ 
 364:         "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 365:         cWMI_Within_Win32_Service & " Where " & _ 
 366:         "TargetInstance isa 'Win32_Service'" 
 ...: 
 370:End If 
 ...: 
 ...:

Although it is possible to specify the service status in the condition statement, E2KWatch does so in the event handler routine. This is only to illustrate that any change to a service triggers an event, even if the service is not mentioned in the configuration file. The event handler, based on the parameters specified in the configuration file, filters the event to determine what actions to take. The same concept applies to other event registrations and handlers.

This means that the filtering is determined at the script level. For example, if you want WMI to filter the event, line 366 must be changed to:

 ...: 
 ...: 
 363: objWMIService.ExecNotificationQueryAsync objWin32_ServiceSink, _ 
 364:   "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 365:   cWMI_Within_Win32_Service & " Where " & _ 
 366:   "TargetInstance isa 'Win32_Service' And TargetInstance.State='Stopped'" 
 ...: 
 ...:

In this case, a WMI event will be triggered only if a service state is modified to a "stopped" state. The filtering is completed by WMI and not by the script.

Many other classes can be used, such as the service name, the start mode, and so on. For more information about available classes and their associated properties, see the WMI SDK at https://msdn.microsoft.com/library.

The WMI Win32_Processor Class 

The Win32_Processor class uses the exact same piece of code as the Win32_Service class. All the remarks made for Win32_Service are valid. Only the properties retrieved are different for each class. See the WMI SDK for more information.

Sample 29 The WMI Win32_Processor class asynchronous event registration 

 ...: 
 ...: 
 372:' Watch the Win32_Processor. 
 373:If UBound (strCPUDeviceID) Then 
 374:   Set objWin32_ProcessorSink = _ 
        WScript.CreateObject ("WbemScripting.SWbemSink","Win32_ProcessorSINK_") 
 375:                                                               
 376:    objWMIService.ExecNotificationQueryAsync objWin32_ProcessorSink, _ 
 377:               "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 378:               cWMI_Within_Win32_Processor & " Where " & _ 
 379:               "TargetInstance isa 'Win32_Processor'" 
 ...: 
 383:End If 
 ...: 
 ...:

The WMI Win32_LogicalDisk Class 

The Win32_LogicalDisk class uses the same piece of code as the Win32_Service class and the Win32_Processor class (see Sample 28), and the same remarks apply.

Note the extension of the condition statement ("TargetInstance.DriveType=3") to receive WMI events only for hard disks (line 392).

Sample 30 The WMI Win32_LogicalDisk class asynchronous event registration 

 ...: 
 ...: 
 385:' Watch the Win32_LogicalDisk. 
 386:If UBound (strLogicalDiskName) Then 
 387:   Set objWin32_LogicalDiskSink = _ 
        WScript.CreateObject("WbemScripting.SWbemSink",Win32_LogicalDiskSINK_") 
 388:                                                         
 389:   objWMIService.ExecNotificationQueryAsync objWin32_LogicalDiskSink, _ 
 390:   "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 391:   cWMI_Within_Win32_LogicalDisk & " Where " & _ 
 392:   "TargetInstance isa 'Win32_LogicalDisk' and TargetInstance.DriveType=3" 
 ...: 
 396:End If 
 ...: 
 ...:

The WMI Win32_NTLogEvent Class 

The Win32_NTLogEvent is a particular case and requires the following two changes:

  • The __InstanceCreationEvent statement must be used instead of the __InstanceModificationEvent statement (line 403). The __InstanceCreationEvent statement tells WMI to send a notification each time a creation event occurs in the specified class. This corresponds to a new event being logged in the Windows 2000 application event log.

  • The "WITHIN" statement is not used, because the Win32_NTLogEvent WMI class has an event provider.

Except for these two changes, the logic is the same as before.

Sample 31 The WMI Win32_NTLogEvent class asynchronous event registration 

 ...: 
 ...: 
 398:' Watch the Win32_NTLogEvent. 
 399:If UBound (strEventLogSourceName) Then 
 400:   Set objWin32_NTLogEventSink = _ 
        WScript.CreateObject("WbemScripting.SWbemSink","Win32_NTLogEventSINK_") 
 401:                                                            
 402:   objWMIService.ExecNotificationQueryAsync objWin32_NTLogEventSink, _ 
 403:              "Select * FROM __InstanceCreationEvent Where " & _ 
 404:              "TargetInstance isa 'Win32_NTLogEvent'" 
 ...: 
 408:End If 
 ...: 
 ...:

The WMI CIM_DATAFile Class 

This WMI initialization is more complex than the previous one, but again the logic is mostly the same. The difference is in the condition statement. The CIM_DATAFile class monitors the size of the .edb and .stm store files. The script programs a WMI event for each store file defined in the configuration file (see Figure 7, lines 56 to 84).

The condition statement for this class specifies the following:

  • The file name for which the WMI event must occur (see Sample 32, lines 419 and 420):

TargetInstance.Name='" & ReplaceBy(strDBPath (intIndice), "", "\")

This line also calls the ReplaceBy function, which replaces any single backslash in the store file path with a double backslash. This is required for the file path syntax.
  • That the event must occur only if the file size is greater than the size specified in the configuration file (lines 421 and 422):

"TargetInstance.FileSize > " & 1024 * CLng(intStoreAlertSize (intIndice))

The file size limit is expressed in megabytes in the configuration file. This explains the "\* 1024" statement.
  • That the event must occur only if the file grows. This is the reason for the statement in line 423:

TargetInstance.FileSize > PreviousInstance.FileSize

PreviousInstance retrieves the characteristics of the object before the modification. With this statement, the condition will be true if the current file size is greater than the prior file size.

Sample 32 The WMI CIM_DATAFile class asynchronous event registration 

 ...: 
 ...: 
 410:' Watch the CIM_DATAFile. 
 411:If UBound (strDNStoreDB) Then 
 412:   Set objCIM_DATAFileSink = WScript.CreateObject _ 
 413:                     ("WbemScripting.SWbemSink", _"E2K_StoreDBSink_") 
 414:   ' Register an asynchronous event for the .edb file. 
 415:   For intIndice = 1 to UBound (strDNStoreDB) 
 416:       objWMIService.ExecNotificationQueryAsync objCIM_DATAFileSink, _ 
 417:        "SELECT * From __InstanceModificationEvent Within " & _ 
 418:        cWMI_Within_CIM_DataFile & " Where " & _ 
 419:        "TargetInstance isa 'CIM_DATAFile' And TargetInstance.Name='" & _ 
 420:        ReplaceBy(strDBPath (intIndice), "\", "\\") & "' And " & _ 
 421:        "TargetInstance.FileSize > " & _ 
 422:        1024 * CLng(intStoreAlertSize (intIndice)) & _ 
 423:        " And TargetInstance.FileSize > PreviousInstance.FileSize" 
 ...: 
 429:   Next 
 430:End If 
 ...: 
 ...:

The WMI NTProcess Class 

Unlike standard WMI classes, the NTProcess class is created with the help of a Managed Object Format (.mof) file. See the WMI SDK for more information about .mof file usage.

Except for the creation and compilation of the .mof file, the logic is the same as before. Because WMI must trigger an event when a process uses more than a certain amount of CPU time, the registration excludes the Idle and _Total processes from the selection. One represents the total free CPU time. The other represents the total CPU time used by all the processes.

Sample 33 The WMI NTProcess *** *** class asynchronous event registration 

 ...: 
 ...: 
 447:If UBound (strProcessName) Then 
 448:   Set objNTProcessSink = WScript.CreateObject("WbemScripting.SWbemSink",_ 
 449:                                                "NTProcessSINK_") 
 450:   objWMINTProcessService.ExecNotificationQueryAsync objNTProcessSink, _ 
 451:         "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 452:         cWMI_Within_NTProcess & " Where " & _ 
 453:         "TargetInstance isa 'NTProcess' and " & _ 
 454:         "TargetInstance.Process != 'Idle' and " & _ 
 455:         "TargetInstance.Process != '_Total'" 
 456:   intRC = WriteToFile (objLogFileName, _ 
 457:      "(WMIEventRegistration) 'NTProcess' " & _ 
                             "asynchronous events registered.") 
 458:End If 
 ...: 
 ...:

The WMI ExchangeServerState Class 

This event registration uses the same logic as the Win32_Service class event registration. No change is made to the logic except the class itself. Any modification of this class will trigger an event. For more information about the ExchangeServerState class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

Sample 34 The WMI ExchangeServerState class asynchronous event registration 

 ...: 
 ...: 
 478:If UBound (strWMIExchangeServerName) Then 
 479:   ' Watch the ExchangeServerState. 
 480:   Set objE2K_ServerStateSink = _ 
        WScript.CreateObject("WbemScripting.SWbemSink","E2K_ServerStateSink_") 
 481:                                                               
 482:   objWMIExchangeService.ExecNotificationQueryAsync _ 
              objE2K_ServerStateSink, _ 
 483:         "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 484:         cWMI_Within_E2K_ServerState & " Where " & _ 
 485:         "TargetInstance isa 'ExchangeServerState'" 
 ...: 
 488:End If 
 ...: 
 ...:

The WMI ExchangeConnectorState Class 

This event registration uses the same logic as the ExchangeServerState class event registration. No change is made to the logic except the class itself. Any modification of this class will trigger an event. For more information about the ExchangeConnectorState class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

Sample 35 The WMI ExchangeConnectorState class asynchronous event registration 

 ...: 
 ...: 
 490:If UBound (strWMIExchangeConnector) Then 
 491:   ' Watch the ExchangeConnectorState. 
 492:   Set objE2K_ConnectorStateSink = _ 
        WScript.CreateObject("WbemScripting.SWbemSink",_ 
                                                "E2K_ConnectorStateSink_") 
 493:                                                             
 494:   objWMIExchangeService.ExecNotificationQueryAsync _ 
          objE2K_ConnectorStateSink, _ 
 495:     "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 496:     cWMI_Within_E2K_Connector & " Where " & _ 
 497:     "TargetInstance isa 'ExchangeConnectorState'" 
 ...: 
 500:End If 
 ...: 
 ...:

The WMI ExchangeLink Class 

This event registration uses the same logic as the ExchangeServerState class event registration, except for the condition statement, which includes the following:

TargetInstance.IncreasingTime > PreviousInstance.IncreasingTime

Like the CIM_DATAFile class, the ExchangeLink* *class requires that WMI trigger an event only if the increasingTime value increases. For more information about the ExchangeLink class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

For more information about the IncreasingTime value, see the Appendix.

Sample 36 The WMI ExchangeLink class asynchronous event registration 

 ...: 
 ...: 
 502:If UBound (strWMIExchangeLink) Then 
 503:   ' Watch the ExchangeLink. 
 504:   Set objE2K_LinkSink = WScript.CreateObject _ 
                                  ("WbemScripting.SWbemSink", _ 
 505:                             "E2K_LinkSink_") 
 506:   objWMIExchangeService.ExecNotificationQueryAsync objE2K_LinkSink, _ 
 507:     "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 508:     cWMI_Within_E2K_Link & " Where " & _ 
 509:     "TargetInstance isa 'ExchangeLink' And " & _ 
 510:     "TargetInstance.IncreasingTime > PreviousInstance.IncreasingTime" 
 ...: 
 513:End If 
 ...: 
 ...:

The WMI ExchangeQueue Class 

This event registration uses the same logic as the ExchangeLink class event registration. Refer to the preceding paragraph for more information.

Sample 37 The WMI ExchangeQueue class asynchronous event registration 

 ...: 
 ...: 
 515:If UBound (strWMIExchangeQueue) Then 
 516:   ' Watch the ExchangeQueue. 
 517:   Set objE2K_QueueSink = WScript.CreateObject _ 
                                   ("WbemScripting.SWbemSink", _ 
 518:                              "E2K_QueueSink_") 
 519:   objWMIExchangeService.ExecNotificationQueryAsync objE2K_QueueSink, _ 
 520:     "Select * FROM __InstanceModificationEvent WITHIN " & _ 
 521:     cWMI_Within_E2K_Queue & " Where " & _ 
 522:     "TargetInstance isa 'ExchangeQueue' And " & _ 
 523:     "TargetInstance.IncreasingTime > PreviousInstance.IncreasingTime" 
 ...: 
 526:End If 
 ...: 
 ...:

The WMI ExchangeClusterResource Class 

This event registration uses the same logic as the ExchangeServerState class event registration. For any change to the ExchangeClusterResource WMI class, WMI will trigger an event. For more information about the ExchangeClusterResource class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

Sample 38 The WMI ExchangeClusterResource class asynchronous event registration 

 ...: 
...: 
528:If UBound (strWMIExchangeClusterVName) Then 
529:   ' Watch the ExchangeClusterResource. 
530:   Set objE2K_ClusterResourceSink =_ 
        WScript.CreateObject ("WbemScripting.SWbemSink", _ 
                                             "E2K_ClusterResourceSink_") 
531:                                                             
532:   objWMIExchangeService.ExecNotificationQueryAsync _ 
         objE2K_ClusterResourceSink, _ 
533:    "Select * FROM __InstanceModificationEvent WITHIN " & _ 
534:    cWMI_Within_E2K_ClusterResource & " Where " & _ 
535:    "TargetInstance isa 'ExchangeClusterResource'" 
536:   intRC = WriteToFile (objLogFileName, "(WMIEventRegistration) " & _ 
537:               "'ExchangeClusterResource' asynchronous events registered.") 
538:  End If 
...: 
...:
Event Routines

The event routine is the function called by the Windows Management Instrumentation (WMI) infrastructure when the registered asynchronous event occurs. The function has two parts:

  • The first part is defined during the event sink object creation and used by the WMI asynchronous event registration (see "The WMI Initialization").

  • The second part is defined by the WMI infrastructure and corresponds to the event triggered by WMI. In this case, OnObjectReady corresponds to an object that is available and provided by an asynchronous call. The object is considered available based on the condition statements in the asynchronous event registration.

As explained for Sample 27, two parameters can be used in an asynchronous event function: objWbemObject and objWbemAsyncContext. 

The E2KWatch.wsf script uses objWbemObject to retrieve the properties of the object for the asynchronous event.

The WMI Win32_Process Class Event Routine 

Because the configuration file may contain more than one service to be monitored, the script executes a "For Each" loop (Sample 39, lines 679 to 706) to retrieve the service name from the list that matches the one provided by WMI.

Note There are many different ways to program WMI events. In this case, the script filters the event after the event is triggered (inside the event handler). This is because the WMI condition set during the WMI asynchronous event initialization is not restrictive (see Sample 28).

You can also program a more restrictive condition during the initialization. For example, you can program WMI to trigger an event only for services listed in the configuration file with specific threshold states. More restrictive approaches offer greater performance, but require the registration of a WMI event for each service to be monitored. This makes the registration process more complex. For educational purposes, the first method is better because it is simpler and allows more events to occur.

After a service match is found (line 681), the script processes the event, using the WMI_GetSvcStatus function to determine the state of the service. This function parses a property of the objWbemObject object (representing the service itself), and returns False if the service is stopped.

If this is the case, the script calls the function WMI_LoopStartServiceRetry. This function executes a loop to restart the service. Three attempts are made (this is a default value defined in the script). When the script exits from this function, the returned value is True if, during the loop, the service has been restarted. Otherwise the returned value is False.

Sample 39 The WMI Win32_Service class asynchronous event handler 

 ...: 
...: 
663:' ------------------------------------------------------------------------- 
664:' Win32_ServiceSink 
665:' ------------------------------------------------------------------------- 
666:Sub Win32_ServiceSink_OnObjectReady (objWbemObject, objWbemAsyncContext) 
667: 
...: 
677:     boolSvcFound = False 
678: 
679:     For intIndice = 1 To Ubound(strServiceName) 
680: 
681:         If Ucase(strServiceName(intIndice)) = _ 
                 Ucase(objWbemObject.TargetInstance.Name) Then 
682:            boolSvcFound = True 
683: 
684:            ' Get the status of the service. 
685:            boolSvcStatus = WMI_GetSvcStatus (objLogFileName, _ 
                                                   objWbemObject.TargetInstance) 
686: 
687:            ' If the service is running, WMI_GetSvcStatus returns True into 
                 ' boolSvcStatus. 
688:            If boolSvcStatus Then 
689:               If strServiceRetryCounter(intIndice) > 0 Then 
694:                  Win32_ServiceInfo objLogFileName, _ 
695:                                    objWbemObject.TargetInstance, _ 
696:                                    objWbemObject.PreviousInstance 
697:               End If 
698:               strServiceRetryCounter(intIndice) = 0 
699:            Else 
700:               boolSvcStatus = WMI_LoopStartServiceRetry (objLogFileName, _ 
701:                                        intIndice, _ 
702:                                        objWbemObject.TargetInstance, _ 
703:                                        boolSvcStatus) 
704:            End If 
705:         End If 
706:     Next 
707: 
708:     If boolSvcFound = False Then 
...: 
713:        intRC = WriteToFile(objLogFileName, _ 
714:                "(Win32_ServiceSink_OnObjectReady) No action taken, " & _ 
715:                "service is not on the list.") 
716:     Else 
717:        ' Check that the boolean returned from WMI_GetSvcStatus is True, 
             ' indicating that the service is running. 
718:        If Not boolSvcStatus Then 
719:           Win32_ServiceInfo objLogFileName, _ 
720:                             objWbemObject.TargetInstance, _ 
721:                             objWbemObject.PreviousInstance 
722:                End If 
723:     End If 
...:  
727:     intRC = WriteToFile (objLogFileName, _ 
728:     "-------------------------------------------------------------------") 
729: 
730:End Sub 
...: 
...:

If the service is not running, the information related to its current status is retrieved (line 719). All the functions in the form ClassNameInfo, such as the function Win32_ServiceInfo, do the same things:

  • Retrieve the current information related to the class

  • Format the data in HTML

  • Send a message to the address specified in the configuration file (the MAILALERTTO parameter at line 10 of Figure 7) with the SendMessage function

The event then terminates.

The WMI Win32_Processor Class Event Routine 

The Win32_Processor event handler uses the same logic as the Win32_Service event handler to retrieve processor names that match those specified in the configuration file. The difference is the action taken. If the processor retrieved (Sample 40, lines 745 and 746) exceeds its associated maximum (lines 747 and 748), the function Win32_ProcessorInfo is invoked to format the related data (lines 749 to 751) and send a message.

Sample 40 The WMI Win32_Processor class asynchronous event handler 

 ...: 
 ...: 
 732:' ----------------------------------------------------------------------- 
 733:' Win32_ProcessorSink 
 734:' ----------------------------------------------------------------------- 
 735:Sub Win32_ProcessorSink_OnObjectReady (objWbemObject, _ 
                                                        objWbemAsyncContext) 
 ...: 
 741:        intRC = WriteToFile (objLogFileName, _ 
 742:                     "(Win32_ProcessorSink_OnObjectReady) Start Sink.") 
 743: 
 744:        For intIndice = 1 To Ubound(strCPUDeviceID) 
 745:            If Ucase(strCPUDeviceID(intIndice)) = _ 
 746:                 Ucase(objWbemObject.TargetInstance.DeviceID) And _ 
 747:               Clng(intCPULoadPercentageMax(intIndice)) <= _ 
 748:                 Clng(objWbemObject.TargetInstance.LoadPercentage) Then 
 749:                    Win32_ProcessorInfo objLogFilename, _ 
 750:                                        objWbemObject.TargetInstance, _ 
 751:                                        objWbemObject.PreviousInstance 
 752:               Exit For 
 753:            End If 
 754:        Next 
 755: 
 756:        intRC = WriteToFile (objLogFileName, _ 
 757:                     "(Win32_ProcessorSink_OnObjectReady) End Sink.") 
 758:        intRC = WriteToFile (objLogFileName, _ 
 759:        "--------------------------------------------------------------") 
 760: 
 761:End Sub 
 ...: 
 ...:

The WMI Win32_LogicalDisk Class Event Routine 

The Win32_LogicalDisk event handler uses the same logic as the Win32_Service event handler to retrieve matches based on the content of the configuration file. The Win32_LogicalDisk event handler is different in two ways:

  • First, the percentage of free disk space is calculated before the logical disk name is filtered because the WMI class does not provide this value (Sample 41, lines 776 and 777).

  • Second, during the loop (lines 779 to 789), if the logical disk name exceeds the maximum set in lines 747 and 748, the function Win32_LogicalDiskInfo is invoked to format the related data (lines 749 to 751) and send a message.

Note that WMI does not make any data calculations or correlations. The event handler performs these tasks (lines 776 and 777).

Sample 41 The WMI Win32_LogicalDisk class asynchronous event handler 

 ...: 
 ...: 
 763:' ------------------------------------------------------------------------ 
 764:' Win32_LogicalDiskSink 
 765:' ------------------------------------------------------------------------ 
 766:Sub Win32_LogicalDiskSink_OnObjectReady (objWbemObject, _ 
                                                          objWbemAsyncContext) 
 ...: 
 773:    intRC = WriteToFile (objLogFileName, _ 
 774:                 "(Win32_LogicalDiskSink_OnObjectReady) Start Sink.") 
 775: 
 776:    intPercentageFree = 100 * _ 
 777:    (objWbemObject.TargetInstance.FreeSpace / _ 
                                       objWbemObject.TargetInstance.Size)  
 778: 
 779:    For intIndice = 1 To Ubound(strLogicalDiskName) 
 780:        If Ucase(strLogicalDiskName(intIndice)) = _ 
 781:             Ucase(objWbemObject.TargetInstance.Name) And _ 
 782:           CLng(intLogicalDiskFreeSpaceMin(intIndice)) >= _ 
 783:             Clng(intPercentageFree) Then 
 784:               Win32_LogicalDiskInfo objLogFileName, _ 
 785:                                     objWbemObject.TargetInstance, _ 
 786:                                     objWbemObject.PreviousInstance 
 787:           Exit For 
 788:        End If 
 789:    Next 
 790: 
 791:    intRC = WriteToFile (objLogFileName, _ 
 792:                 "(Win32_LogicalDiskSink_OnObjectReady) End Sink.") 
 793:    intRC = WriteToFile (objLogFileName, _ 
 794:    "------------------------------------------------------------------") 
 795: 
 796:End Sub 
 ...: 
 ...:

The WMI Win32_NTLogEvent Class Event Routine 

The Win32_NTLogEvent event handler uses the same logic as the other event handlers, but with more sophisticated code for event filtering. The configuration file (Figure 7, lines 90 to 96) allows a wildcard, which lets you determine whether an event is significant to the event handler. The filtering process can be summarized as a pure string manipulation (Sample 42, lines 819 to 848).

After the string comparisons and substitutions are made, the validation tests (lines 850 to 861) are executed to determine whether an alert must be sent by way of the Win32_NTLogEventInfo function (line 860).

Sample 42 The WMI Win32_NTLogEvent class asynchronous event handler 

 ...: 
...: 
798:' ------------------------------------------------------------------------ 
799:' Win32_NTLogEventSink 
800:' ------------------------------------------------------------------------ 
801:Sub Win32_NTLogEventSink_OnObjectReady (objWbemObject, objWbemAsyncContext) 
...:  
814:  intRC = WriteToFile (objLogFileName, _ 
815:                 "(Win32_NTLogEventSink_OnObjectReady) Start Sink.") 
816: 
817:  For intIndice = 1 To Ubound(strEventLogSourceName) 
818: 
819:    If strEventLogSourceName(intIndice) = "*" Then 
820:       strTempEventLogSourceName = _ 
                     CheckIfNull(objWbemObject.TargetInstance.SourceName) 
821:    Else 
822:       strTempEventLogSourceName = strEventLogSourceName(intIndice) 
823:    End If 
824: 
825:    If intEventLogEventCode(intIndice) = "*" Then 
826:       intTempEventLogEventCode = _ 
                     CheckIfNull(objWbemObject.TargetInstance.EventCode) 
827:    Else 
828:       intTempEventLogEventCode = intEventLogEventCode(intIndice) 
829:    End If 
830: 
831:    If strEventLogCategoryString(intIndice) = "*" Then 
832:       strTempEventLogCategoryString = _ 
                 CheckIfNull(objWbemObject.TargetInstance.CategoryString) 
833:    Else 
834:       ' Add a CRLF because the original objWbemObject always has a CRLF. 
835:       strTempEventLogCategoryString = _ 
                     strEventLogCategoryString(intIndice) & vbCRLF 
836:    End If 
837: 
838:    If strEventLogLogfile(intIndice) = "*" Then 
839:       strTempEventLogLogfile = _ 
                 CheckIfNull(objWbemObject.TargetInstance.Logfile) 
840:    Else 
841:       strTempEventLogLogfile = strEventLogLogfile(intIndice) 
842:    End If 
843: 
844:    If strEventLogType(intIndice) = "*" Then 
845:       strTempEventLogType = _ 
                 CheckIfNull(objWbemObject.TargetInstance.Type) 
846:    Else 
847:       strTempEventLogType = strEventLogType(intIndice) 
848:    End If 
849: 
850:    If Ucase(strTempEventLogSourceName) = _ 
851:        Ucase(CheckIfNull(objWbemObject.TargetInstance.SourceName)) And  
852:       CLng(intTempEventLogEventCode) = _ 
853:        CLng(CheckIfNull(objWbemObject.TargetInstance.EventCode)) And _ 
854:       Ucase(strTempEventLogCategoryString) = _ 
855:        Ucase(CheckIfNull(objWbemObject.TargetInstance.CategoryString))And  
856:       Ucase(strTempEventLogLogfile) = _ 
857:        Ucase(CheckIfNull(objWbemObject.TargetInstance.Logfile)) And _ 
858:       Ucase(strTempEventLogType) = _ 
859:        Ucase(CheckIfNull(objWbemObject.TargetInstance.Type)) Then 
860:      Win32_NTLogEventInfo objLogFileName, objWbemObject.TargetInstance 
861:            Exit For 
862:    End If 
863:  Next 
864: 
865:  intRC = WriteToFile (objLogFileName, _ 
866:                       "(Win32_NTLogEventSink_OnObjectReady) End Sink.") 
867:  intRC = WriteToFile (objLogFileName, _ 
868:  "---------------------------------------------------------------------") 
869: 
870:End Sub 
...: 
...:

The WMI CIM_DATAFile Class Event Routine 

Again, the CIM_DATAFile event handler uses the same logic as other event handlers, but it takes some additional actions. The script monitors the size of the specific files that represent the store. As explained previously (see Figure 8, lines 56 to 84), two thresholds are specified in the configuration file:

  • When the first threshold is reached, an alert is sent by way of the E2K_StoreDBInfo function (Sample 43, lines 965 to 967).

  • When the second threshold is reached (line 888), the DismountStoreDB function automatically dismounts the store (line 954).

Before dismounting the store, the script sends alerts with the WMI information available (lines 889 to 942) and waits for a time-out (line 943).

Sample 43 The WMI CIM_DATAFile class asynchronous event handler 

 ...: 
...: 
873:' ------------------------------------------------------------------------- 
874:' E2K_StoreDBSink 
875:' ------------------------------------------------------------------------- 
876:Sub E2K_StoreDBSink_OnObjectReady (objWbemObject, objWbemAsyncContext) 
...: 
882:  intRC = WriteToFile (objLogFileName, _ 
883:                       "(E2K_StoreDBSink_OnObjectReady) Start Sink.") 
884: 
885:  For intIndice = 1 To Ubound(strDNStoreDB) 
886:    If UCase(objWbemObject.TargetInstance.Name) = _ 
           UCase(strDBPath (intIndice)) Then 
887: 
888:       If intStoreDismountSize (intIndice) < _ 
              objWbemObject.TargetInstance.FileSize Then 
889:          strHTML = cStartREDHTML & "WARNING:" & _ 
890:                    cBacktoNormalHTML & "<BR>Dismounting store '" & _ 
891:                    strStoreName(intIndice) & _ 
892:                    "' (" & strStoreDBClass(intIndice) & ") in " & _ 
893:                    cWaitBeforeDBDismount / 1000 & "s. on " & _ 
894:                    strComputerName & " (E2K_StoreDBInfo)" 
895: 
896:          strHTML = cStartHTMLTableTitle & strHTML & cEndHTMLTableTitle 
897:          strHTML = strHTML & cStartHTMLTableData 
898:          strHTML = strHTML & FormatHTML ("", _ 
899:                                          "<B>Current State</B>", _ 
900:                                          "<B>Previous State</B>") 
901:          strHTML = strHTML & FormatHTML ("Name: ", _ 
902:                    objWbemObject.TargetInstance.Name, _ 
903:                    objWbemObject.PreviousInstance.Name) 
904:          strHTML = strHTML & _ 
905:                    FormatHTML ("Filesize: ", _ 
906:                            objWbemObject.TargetInstance.FileSize / 1024, _ 
907:                            objWbemObject.PreviousInstance.FileSize / 1024) 
908:          strHTML = strHTML & FormatHTML ("Encrypted: ", _ 
909:                            objWbemObject.TargetInstance.Encrypted, _ 
910:                            objWbemObject.PreviousInstance.Encrypted) 
911:          strHTML = strHTML & FormatHTML ("FileType: ", _ 
912:                            objWbemObject.TargetInstance.FileType, _ 
913:                            objWbemObject.PreviousInstance.FileType) 
914:          strHTML = strHTML & FormatHTML ("LastAccessed: ", _ 
915:                            objWbemObject.TargetInstance.LastAccessed, _ 
916:                            objWbemObject.PreviousInstance.LastAccessed) 
917:          strHTML = strHTML & FormatHTML ("LastModified: ", _ 
918:                            objWbemObject.TargetInstance.LastModified, _ 
919:                            objWbemObject.PreviousInstance.LastModified) 
920:          strHTML = strHTML & cEndHTMLTableData 
921: 
922:          AlertHandler objLogFileName, _ 
923:                "(E2K_StoreDBSink)", _ 
924:                "Informationstore '" & strStoreName(intIndice) & _ 
925:                "' (Size=" & objWbemObject.TargetInstance.FileSize / 1024 &  
926:                " Kbytes) will be dismounted in " & _ 
927:                cWaitBeforeDBDismount / 1000 & ".", _ 
928:                strHTML, _ 
929:                cMailAlert, _ 
930:                cEventLogAlert, _ 
931:                cCommandAlert, _ 
932:                cPopupAlert 
933: 
934:          AlertHandler objLogFileName, _ 
935:            "(E2K_StoreDBSink)", _ 
936:            "Pausing " & cWaitBeforeDBDismount / 1000 & _ 
                                                        "s. before action.",  
937:            strHTML, _ 
938:            False, _ 
939:            False, _ 
940:            False, _ 
941:            False 
942: 
943:          WScript.Sleep (cWaitBeforeDBDismount) 
944: 
945:          AlertHandler objLogFileName, _ 
946:                   "(E2K_StoreDBSink)", _ 
947:                   "Dismounting store '" & _ 
                                      strStoreName(intIndice) & "'.", _ 
948:                   strHTML, _ 
949:                   False, _ 
950:                   False, _ 
951:                   False, _ 
952:                   False 
953: 
954:          DismountStoreDB (strDNStoreDB (intIndice)) 
955: 
956:          AlertHandler objLogFileName, _ 
957:                   "(E2K_StoreDBSink)", _ 
958:                   "Store '" & strStoreName(intIndice) & "' dismounted.", _ 
959:                   strHTML, _ 
960:                   cMailAlert, _ 
961:                   cEventLogAlert, _ 
962:                   cCommandAlert, _ 
963:                   cPopupAlert 
964:      Else 
965:          E2K_StoreDBInfo objLogFileName, _ 
966:                  objWbemObject.TargetInstance, _ 
967:                  objWbemObject.PreviousInstance 
968:      End If 
969: 
970:    End If 
971:Next 
972: 
973:intRC = WriteToFile (objLogFileName, _ 
                        "(E2K_StoreDBSink_OnObjectReady) End Sink.") 
974:intRC = WriteToFile (objLogFileName, _ 
975:        "---------------------------------------------------------------") 
976: 
977:End Sub 
...: 
...:

The WMI NTProcess Class Event Routine 

The NTProcess event handler uses the same logic as the Win32_Processor event handler (see Sample 39) to retrieve process names that match those specified in the configuration file. The difference is that the NTProcess event handler allows a wildcard. The string manipulation logic is the same as that in Sample 42 for the Win32_NTLogEvent class.

If a retrieved process (Sample 44, lines 1151 and 1152) exceeds its associated maximum (lines 1153 and 1154), the function Win32_NTProcessInfo is invoked to format the related data (lines 1155 to 1157) and send a message.

Sample 44 The WMI NTProcess class asynchronous event handler 

....: 
....: 
1131:' ------------------------------------------------------------------- 
1132:' NTProcessSink 
1133:' ------------------------------------------------------------------- 
1134:Sub NTProcessSink_OnObjectReady (objWbemObject, objWbemAsyncContext) 
1135: 
1136:Dim strTempProcessName 
1137: 
1138:   On Error Resume Next 
1139: 
1140:   intRC = WriteToFile (objLogFileName, _ 
1141:                "(NTProcessSink_OnObjectReady) Start Sink.") 
1142: 
1143:   For intIndice = 1 To Ubound(strProcessName) 
1144: 
1145:       If strProcessName(intIndice) = "*" Then 
1146:          strTempProcessName = objWbemObject.TargetInstance.Process 
1147:       Else 
1148:          strTempProcessName = strProcessName(intIndice) 
1149:       End If 
1150: 
1151:       If Ucase(strTempProcessName) = _ 
1152:            Ucase(objWbemObject.TargetInstance.Process) And _ 
1153:          CLng(intProcessCPUPercentageMax(intIndice)) <= _ 
1154:            Clng(objWbemObject.TargetInstance.PercentageProcessTime) Then 
1155:               Win32_NTProcessInfo objLogFileName, _ 
1156:                                   objWbemObject.TargetInstance, _ 
1157:                                        objWbemObject.PreviousInstance 
1158:               Exit For 
1159:       End If 
1160:   Next 
1161: 
1162:   intRC = WriteToFile (objLogFileName, _ 
1163:                        "(NTProcessSink_OnObjectReady) End Sink.") 
1164:   intRC = WriteToFile (objLogFileName, _ 
1165:           "-----------------------------------------------------------") 
....: 
....:

The WMI ExchangeServerState *** *** Class Event Routine 

The ExchangeServerState event handler logic is the same as the other WMI event handlers. Any modification of an instance of the class triggers a WMI event (see Sample 45). When the loop (lines 990 to 997) matches the triggered event, the E2K_ServerStateInfo function is called (lines 993 to 995) to format the data and send a message.

Sample 45 The WMI ExchangeServerState class asynchronous event handler 

  ...: 
  ...: 
  980:' ----------------------------------------------------------------------- 
  981:' E2K_ServerStateSink 
  982:' ----------------------------------------------------------------------- 
  983:Sub E2K_ServerStateSink_OnObjectReady (objWbemObject,   objWbemAsyncContext) 
  984: 
  985:   On Error Resume Next 
  986: 
  987:   intRC = WriteToFile (objLogFileName, _ 
  988:                "(E2K_ServerStateSink_OnObjectReady) Start Sink.") 
  989: 
  990:   For intIndice = 1 To Ubound(strWMIExchangeServerName) 
  991:       If Ucase(strWMIExchangeServerName(intIndice)) = _ 
  992:            Ucase(objWbemObject.TargetInstance.Name) Then 
  993:                E2K_ServerStateInfo objLogFileName, _ 
  994:                                    objWbemObject.TargetInstance, _ 
  995:                                    objWbemObject.PreviousInstance 
  996:       End If 
  997:   Next 
  998: 
  999:   intRC = WriteToFile (objLogFileName, _ 
1000:                "(E2K_ServerStateSink_OnObjectReady) End Sink.") 
1001:   intRC = WriteToFile (objLogFileName, _ 
1002:           "-----------------------------------------------------------") 
1003: 
1004:End Sub 
....: 
....:

The WMI ExchangeConnectorState Class Event Routine 

The event handler for the ExchangeConnectorState class uses exactly the same logic as the event handler for ExchangeServerState. See "The WMI ExchangeServerState Class" for more information.

Sample 46 The WMI ExchangeConnectorState class asynchronous event handler 

....: 
....: 
1006:' ------------------------------------------------------------------------ 
1007:' E2K_ConnectorStateSink 
1008:' ------------------------------------------------------------------------ 
1009:Sub E2K_ConnectorStateSink_OnObjectReady (objWbemObject, _ 
                                                           objWbemAsyncContext) 
1010: 
1011:    On Error Resume Next 
1012: 
1013:    intRC = WriteToFile (objLogFileName, _ 
1014:                 "(E2K_ConnectorStateSink_OnObjectReady) Start Sink.") 
1015: 
1016:    For intIndice = 1 To Ubound(strWMIExchangeConnector) 
1017:        If Ucase(strWMIExchangeConnector(intIndice)) = _ 
1018:             Ucase(objWbemObject.TargetInstance.Name) Then 
1019:                 E2K_ConnectorStateInfo objLogFileName, _ 
1020:                                        objWbemObject.TargetInstance, _ 
1021:                                        objWbemObject.PreviousInstance 
1022:        End If 
1023:    Next 
1024: 
1025:    intRC = WriteToFile (objLogFileName, _ 
1026:                 "(E2K_ConnectorStateSink_OnObjectReady) End Sink.") 
1027:    intRC = WriteToFile (objLogFileName, _ 
1028:            "-----------------------------------------------------------") 
1029:       
1030:     End Sub 
....: 
....:

The WMI ExchangeLink Class Event Routine 

The ExchangeLink class event handler allows a wildcard for the link name. It uses the same string manipulation logic as Sample 42 and Sample 44. The E2K_LinkInfo function (see Sample 47) is called if the following conditions are true:

  • The link name matches a link name specified in the configuration file (see Figure 7, line 115).

  • The increasingTime value reaches the threshold specified in the configuration file.

Sample 47 The WMI ExchangeLink class asynchronous event handler 

          1         2         3         4         5         6         7        x 
1234567890123456789012345678901234567890123456789012345678901234567890123456789 
....: 
....: 
1032:' ------------------------------------------------------------------------ 
1033:' E2K_LinkSink 
1034:' ------------------------------------------------------------------------ 
1035:Sub E2K_LinkSink_OnObjectReady (objWbemObject, objWbemAsyncContext) 
1036: 
1037:Dim strTempWMILinkName 
1038: 
1039:        On Error Resume Next 
1040: 
1041:        intRC = WriteToFile (objLogFileName, _ 
1042:                     "(E2K_LinkSink_OnObjectReady) Start Sink.") 
1043: 
1044:        For intIndice = 1 To Ubound(strWMIExchangeLink) 
1045:            If strWMIExchangeLink(intIndice) = "*" Then 
1046:               strTempWMILinkName = objWbemObject.TargetInstance.LinkName 
1047:            Else 
1048:               strTempWMILinkName = strWMIExchangeLink(intIndice) 
1049:            End If 
1050: 
1051:            If UCase(strTempWMILinkName) = _ 
1052:                 UCase (objWbemObject.TargetInstance.LinkName) And _ 
1053:               CLng(intWMIExchangeLinkIncreasingTime(intIndice)) <= _ 
1054:                 CLng(objWbemObject.TargetInstance.IncreasingTime) Then 
1055:                     E2K_LinkInfo objLogFileName, _ 
1056:                     objWbemObject.TargetInstance, _ 
1057:                     objWbemObject.PreviousInstance 
1058:            End If 
1059:        Next 
1060: 
1061:        intRC = WriteToFile (objLogFileName, _ 
1062:                             "(E2K_LinkSink_OnObjectReady) End Sink.") 
1063:        intRC = WriteToFile (objLogFileName, _ 
1064:                "-------------------------------------------------------") 
1065: 
1066:End Sub 
....: 
....:

The WMI ExchangeQueue Class Event Routine 

The event handler for the ExchangeQueue class uses the same logic as that for the ExchangeLink class. See "The WMI ExchangeLink Class" for more information.

Sample 48 The WMI ExchangeQueue class asynchronous event handler 

....: 
....: 
1068:' ------------------------------------------------------------------------ 
1069:' E2K_QueueSink 
1070:' ------------------------------------------------------------------------ 
1071:Sub E2K_QueueSink_OnObjectReady (objWbemObject, objWbemAsyncContext) 
1072: 
1073:Dim strTempWMIQueueName 
1074: 
1075:    On Error Resume Next 
1076: 
1077:    intRC = WriteToFile (objLogFileName, _ 
1078:                         "(E2K_QueueSink_OnObjectReady) Start Sink.") 
1079: 
1080:    For intIndice = 1 To Ubound(strWMIExchangeQueue) 
1081:        If strWMIExchangeLink(intIndice) = "*" Then 
1082:           strTempWMIQueueName = objWbemObject.TargetInstance.QueueName 
1083:        Else 
1084:           strTempWMIQueueName = strWMIExchangeQueue(intIndice) 
1085:        End If 
1086: 
1087:        If Ucase(strTempWMIQueueName) = _ 
1088:             UCase (objWbemObject.TargetInstance.QueueName) And _ 
1089:           CLng(intWMIExchangeQueueIncreasingTime(intIndice)) <= _ 
1090:             CLng(objWbemObject.TargetInstance.IncreasingTime) Then 
1091:                 E2K_QueueInfo objLogFileName, _ 
1092:                               objWbemObject.TargetInstance, _ 
1093:                               objWbemObject.PreviousInstance 
1094:        End If 
1095:    Next 
1096: 
1097:    intRC = WriteToFile (objLogFileName, _ 
1098:                         "(E2K_QueueSink_OnObjectReady) End Sink.") 
1099:    intRC = WriteToFile (objLogFileName, _ 
1100:            "-----------------------------------------------------------") 
1101: 
1102:End Sub 
....: 
....:

The WMI ExchangeClusterResource Class Event Routine

The event handler for the ExchangeClusterResource uses the same logic as that for the ExchangeServerState. See "The WMI ExchangeServerState Class" for more information. Note that this event is relevant only if you run the script with an Exchange 2000 cluster.

Sample 49 The WMI ExchangeClusterResource class asynchronous event handler 

....: 
....: 
1104:' ------------------------------------------------------------------------ 
1105:' E2K_ClusterResourceSink 
1106:' ------------------------------------------------------------------------ 
1107:Sub E2K_ClusterResourceSink_OnObjectReady (objWbemObject, _ 
                                                          objWbemAsyncContext) 
1108: 
1109:    On Error Resume Next 
1110: 
1111:    intRC = WriteToFile (objLogFileName, _ 
1112:                 "(E2K_ClusterResourceSink_OnObjectReady) Start Sink.") 
1113: 
1114:    For intIndice = 1 To Ubound(strWMIExchangeClusterVName) 
1115:        If Ucase(strWMIExchangeClusterVName(intIndice)) = _ 
1116:             Ucase(objWbemObject.TargetInstance.VirtualMachine) Then 
1117:                 E2K_ClusterResourceInfo objLogFileName, _ 
1118:                                         objWbemObject.TargetInstance, _ 
1119:                                         objWbemObject.PreviousInstance 
1120:        End If 
1121:    Next 
1122: 
1123:    intRC = WriteToFile (objLogFileName, _ 
1124:                 "(E2K_ClusterResourceSink_OnObjectReady) End Sink.") 
1125:    intRC = WriteToFile (objLogFileName, _ 
1126:            "-----------------------------------------------------------") 
1127: 
1128:End Sub 
....: 
....:

The E2KWatch script includes a large set of helper functions that allow you to use the script in a production environment. Helper functions include the following.

Log file creation

The CreateLogFile function creates a local log file containing a trace activity of the script. The log file name is a default log file specified in the script as a constant.

HTML formatting

The FormatHTML function manipulates strings to insert various HTML tags. The purpose is to build an HTML body with WMI data. This HTML body is sent by the SendMessage function.

Alert handling

The AlertHandler function is called by the ClassNameInfo function to send messages. The AlertHandler can also generate a popup window or invoke a default .cmd file "ALERT.CMD" when an alert occurs.

Error handling

The ErrorHandler function has the same structure as the AlertHandler. The difference is that the ErrorHandler attempts to correct errors as they occur during the script execution.

Reading the configuration file

The ReadConfigurationFile function reads and parses files and makes basic data validation decisions. The configuration file can be specified in the command line or, if no file is specified, when the script prompts the user for a configuration file.

Dismounting stores

The DismountStore function is used by the CIM_DATAFile WMI class event handler (see Sample 43, line 954). This function uses the CDOEXM method to dismount a store (see Sample 9 for a mailbox store or Sample 16 for a public store).

Retrieving store class

The GetStoreDBClass function determines, based on the distinguished name of the store specified in the configuration file, if it is a mailbox store or public store. This information is passed to the DismountStore function.

Retrieving the .edb file path

The GetStoreMDBPaths function helps determine the path of the .edb file based on the distinguished name of the store specified in the configuration file.

Retrieving the .stm file path

The GetStoreSTMPaths function helps determine the path of the .stm file based on the distinguished name of the store specified in the configuration file. This function has the same logic as the GetStoreMDBPaths function.

Sending messages

The Send function helps the ClassNameInfo function to send formatted HTML data by mail. This function uses the logic in
Sample 23 (line 40 for the HTMLBody and line 76 for the Send function).

Possible Enhancements

The E2KWatch leaves plenty of room for improvement. Possible adaptations are infinite and will probably never perfectly match everyone's needs. Nevertheless, here are some general functions that can be added to the script:

  • The script sends a message if CPU usage (process or processor) reaches a certain threshold. A helpful enhancement is to send an alert if the CPU usage threshold is maintained for a certain period of time. In a real business environment, it is normal to have some CPU peak usage without any problems. On the other hand, having a CPU usage of 90 percent for 15 minutes or more can indicate a serious performance problem. The script, as it is here, would send many alerts, even for normal peak usage.

  • As the script is here, if a monitored service is changed from a manual or automatic startup mode to a disabled startup mode, the script tries to start the service anyway. This results in an error because the service manager does not allow a disabled service to start. Another possible enhancement would remedy this situation.

  • The script saves all server activity in a log file. In its current version, this file is never purged. A helpful enhancement would use the WMI event timer to purge or archive the log each day.

  • The configuration file holds all the parameters for the script. When a change is made to this file, is it necessary to restart the script to put the changes into effect. You might want to program an asynchronous event with the CIM_DATAFile class to monitor the configuration file. With this enhancement, when a change is made to the file, the script cancels all WMI asynchronous events registered, substitutes data from the updated configuration file, and reprograms all the events.

  • The script monitors the links with the ExchangeLink class based on the increasingTime value. If the link is a dial-up link, the script sends an alert because the number of messages in the queue increases regularly. But in the case of a scheduled link, this is normal because the link waits for the next scheduled time to establish the connection. Another enhancement would allow the script to take this particularity into account.

The following screen shot illustrates what happens when you run the E2KWatch script.

Cc750307.ex2kw06(en-us,TechNet.10).gif

Figure 8 A sample E2KWatch mail alert for the increasingTime value

Conclusion

Microsoft® Exchange 2000 Server offers a set of concepts and technologies oriented to the future. These technologies are based on industry standards such as LDAP and X.500, WebDAV, XML, IMAP4, SMTP, X.400, and so on. Learning about these standards is a big step toward learning Microsoft® Windows® 2000 and Exchange 2000.

By adding COM abstraction layers based on these standards (for instance, CDO for Exchange 2000 or WebDAV), Microsoft has moved its Exchange technology to Internet standards. The knowledge of the COM object model in environments like Windows 2000 and Exchange 2000 is key to developing new applications.

From an enterprise management point of view, Windows Management Instrumentation (WMI) and CDO for Exchange Management (CDOEXM) are two key players

Developing under Windows 2000 and Exchange 2000 changes the challenges from learning the programming language to learning the industry standards and the COM abstraction layers.

Appendix

An Overview of Exchange 2000 WMI Providers

Exchange 2000 setup adds three WMI providers: the Exchange Routing Table provider, the Exchange Queue provider, and the Exchange Cluster provider. These providers allow any application to access Exchange 2000 management information. Each of the three providers relates to a specific component set of Exchange 2000.

  • The Exchange Routing Table provider runs on top of the routing API.

  • The Exchange Queue provider runs on top of the Queue API.

  • The Exchange Cluster Provider runs on top of the Cluster API.

These three providers expose a set of classes available from the root\CIMV2\Applications\Exchange WMI namespace.

Exchange 2000 Service Pack 2 adds two new WMI providers:

  • The Exchange Message Tracking provider runs on top of the message tracking API.

  • The Exchange DS Access provider runs on top of the DSAccess API.

These two providers expose a set of classes available from the root\MicrosoftExchangeV2 WMI namespace.

These providers deliver information to help diagnose system problems and notify the appropriate system components to solve them.

Figure 9 is a diagram of the WMI classes.

Figure 9 Exchange 2000 WMI Classes

Figure 9 Exchange 2000 WMI Classes

The Exchange 2000 Routing Table Provider

The Exchange Routing Table WMI provider works on top of the Exchange Transport Core. The purpose of this provider is:

  • To publish the status of the local Exchange 2000 server in the routing table, based on the monitoring conditions configured with the Exchange System Manager (ESM).

  • To retrieve the states of other Exchange 2000 servers in the organization from the routing table, based on the monitoring conditions configured with the ESM.

  • To publish the states of the Exchange 2000 local connectors in the routing table.

  • To retrieve the states of other Exchange 2000 connectors in the organization from the routing table.

To perform these tasks, the Routing Table provider implements a path to and from the routing table for the system attendant only. Every Exchange 2000 server in the organization publishes its states in the routing table in this way. This means that it is possible to retrieve the states of all the servers and connectors in the organization from any individual server.

To distinguish between server information and connector information, the Routing Table provider implements two WMI classes: ExchangeServerState and ExchangeConnectorState.

Each of these classes has a specific set of properties.

The ExchangeServerState Class

The states retrieved from this class are based on the monitoring conditions specified with the ESM. With the ESM (Figure 10), you can define a monitoring condition with a corresponding state for:

  • Queues

  • Disk space available on any disk in the system

  • Memory usage

  • The CPU usage for a "warning" or a "critical" threshold

  • Other Windows 2000 services relevant to Exchange 2000

    Figure 10 Exchange System Manager configuration settings for monitoring

    Figure 10 Exchange System Manager configuration settings for monitoring

    Figure 11 Exchange System Manager configuration settings for CPU monitoring

    Figure 11 Exchange System Manager configuration settings for CPU monitoring

The ESM monitoring configuration user interface is saved as a string in the Configuration Naming Context of Microsoft® Active Directory®. No direct programming of the WMI infrastructure is done from the ESM. The System Attendant programs WMI by reading the same string from Active Directory Configuration Naming context.

Cc750307.ex2kw10(en-us,TechNet.10).gif

Figure 12 The Exchange System Manager Status view

The ESM accesses the Exchange Routing Table provider through the WMI MGMT layer (see Figure 9) and displays the ServerStateString property of the ExchangeServerState class and the IsUp property from the ConnectorState class. The ESM display is refreshed at regular intervals or can be refreshed by the administrator.

This is what you see in the ESM view in Figure 12.

For complete information about the ExchangeServerState WMI class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

The ExchangeConnectorState Class

The ExchangeConnectorState class is based on the same principle as the ExchangeServerState class. The ExchangeConnectorState class monitors the connectors in an Exchange organization and publishes their states in the routing table. When this class is interrogated from ESM or by a script, it offers a list of the connectors available from the routing table with their corresponding states.

For complete information about the ExchangeConnectorState WMI class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

The Exchange 2000 WMI Queue Provider

The WMI Queue provider is based on the Queue API. It does not use any mechanism to publish or retrieve information from the routing table with the help of the WMI Routing Table provider. The scope of this provider is local to the Exchange 2000 server. The provider implements two WMI classes:

  • The ExchangeLink class to retrieve information about the Exchange links directly from the Queue API.

  • The ExchangeQueue class to retrieve information about the Exchange queues directly from the Queue API.

From a scripting and management perspective, the most interesting data for both classes is the IncreasingTime property (see Sample 3 and Sample 4). The Queue API does not provide this property directly. Instead, it is calculated by the WMI Queue provider.

Figure 13 The IncreasingTime property behavior from the ExchangeLink and ExchangeQueue classes

Figure 13 The IncreasingTime property behavior from the ExchangeLink and ExchangeQueue classes

The IncreasingTime value represents the length of time during which the number of messages in the link or queue has not decreased. The time is returned in milliseconds. To monitor this property, it is important to understand how the calculation is made. Two important factors influence how to monitor the value:

  • The sampling frequency (Tx interval on Figure 13)

  • The IncreasingTime value threshold to determine a critical situation

Each time a sample is taken, the IncreasingTime property is calculated and a new value is determined. The first polling interval (A on Figure 13) always returns an IncreasingTime value of zero because it is the first sample read. But when the second polling interval occurs (A' on Figure 13), the IncreasingTime value will be equal to the T1 interval because the number of messages has not decreased between A and A'.

When the next polling interval occurs (B on Figure 13), the IncreasingTime value will be equal to zero because the number of messages has decreased between A' and B. Note that the number of messages has decreased below the prior measured level. If the number of messages does not decrease below the prior level, the IncreasingTime value is equal to T1+T2. If the number of messages decreases to below the prior level, then the IncreasingTime value is set to zero.

For the same situation, if the polling interval is changed to measure at point A for the first sample and at point C for the second sample, the IncreasingTime value will not be equal to zero because the number of messages has not been detected as decreasing below the number of messages measured at point A. The IncreasingTime value will be equal to T1+T2+T3+T4.

Furthermore, if the polling is done between:

  • A and B: IncreasingTime is equal to T1+T2.

  • B and C: IncreasingTime is equal to T1+T2+T3+T4.

  • C and D: IncreasingTime is equal to T1+T2+T3+T4+T5+T6.

  • D and E: IncreasingTime is equal to zero.

If the polling is done between:

  • A and A': IncreasingTime is equal to T1.

  • A' and B: IncreasingTime is equal to zero.

  • B and B': IncreasingTime is equal to T3.

  • B' and C: IncreasingTime is equal to T3+T4.

  • C and C': IncreasingTime is equal to T3+T4+T5.

  • C' and D: IncreasingTime is equal to T3+T4+T5+T6.

  • D and D': IncreasingTime is equal to zero.

  • D' and E: IncreasingTime is equal to T8.

  • E and E': IncreasingTime is equal to zero.

By changing the sampling frequency, you see that the IncreasingTime value can be very different. With the slowest sampling frequency, you miss the decreasing period between A and B. With the fastest polling frequency, this decreasing period is detected. But what if you want to look at this behavior on a real-time scale?

Suppose that the Tx interval is 1 minute, the alert threshold for the IncreasingTime value is 30000 milliseconds (30 seconds) and the polling interval is Tx (every 1 minute). In this case, you get a notification at point A'. This is useless because the queue has decreased between A' and B. In a production environment the number of messages can increase suddenly for 1 minute (or more), but the number of messages can decrease just as rapidly. Again, you get an alert for a non-critical situation.

Now, suppose that the Tx interval is 1 hour, the alert threshold for the IncreasingTime value is 14400000 milliseconds (4 hours) and the polling interval is Tx (every 1 hour). In this case, you will only get a notification at point D. This is a better situation, because you are notified for a non-decreasing situation after 4 hours. This is too long to wait for a problem to be noted.

The key is to find a polling interval to quickly detect any non-decreasing situations without getting an alert for temporary increasing situations.

A best practice might be to poll every 10 seconds and to set an IncreasingTime threshold value of 1800000 milliseconds (30 minutes). In this case, the flow represented in Figure 13 does not generate an alert. Only real non-decreasing situations longer than 30 minutes will be detected with a precision of 10 seconds.

For complete information about the ExchangeLink and ExchangeQueue WMI classes, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

The Exchange 2000 WMI Cluster Provider

The Cluster WMI provider implements only one class directly based on the cluster service, the ExchangeClusterResource WMI class. The State property contains the state of the cluster group.

For complete information about the ExchangeClusterResource WMI class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

The Exchange 2000 WMI Message Tracking Provider

The Message Tracking WMI provider implements only one class directly based on the Message Tracking service, the Exchange_MessageTrackingEntry class.

For complete information about the Exchange_MessageTrackingEntry WMI class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

The Exchange 2000 WMI DS Access DC Provider

The DS Access WMI provider implements only one class directly based on the DS Access API, the Exchange_DSAccessDC WMI class.

For complete information about the Exchange_DSAccessDC WMI class, including property descriptions, see the Exchange SDK at https://msdn.microsoft.com/exchange.

ADSI Helper Functions

Note This document does not focus on ADSI. See the MSDN Library or the Compaq Active Answers Web site at https://activeanswers.compaq.com/ for more information about ADSI search operations.

Note Addresses of third-party Web sites referenced in this white paper are provided to help you find the information you need. This information is subject to change without notice. Microsoft in no way guarantees the accuracy of this third-party information.

Getting the Exchange Organization Name

The Exchange Organization name is located in the Active Directory Configuration Naming Context. Only one Exchange 2000 organization in each Windows 2000 forest is supported. A simple Active Directory Service Interfaces (ADSI) script can retrieve the Exchange organization name by looking in the container dedicated to Exchange 2000. This is the purpose of Sample 50. The script makes a loop (lines 33 to 41) and returns the cn (line 38) of the first msExchOrganizationContainer object class found. If a forest could support many Exchange organizations, this method would not be valid. Under Windows 2000 and Exchange 2000, only one organization is supported.

This function is used in Sample 6, in Sample 25, and in Sample 26.

Sample 50 Retrieving the Exchange Organization name 

   1:' This VBScript script verifies that Exchange 2000 is installed, and 
     ' returns the organization name. 
   .: 
   8:Option Explicit 
   9: 
  10:' ------------------------------------------------------------------------ 
  11:Function GetExchangeOrg () 
  ..: 
  ..: 
  18:        On Error Resume Next 
  19: 
  20:        Set ObjRoot = GetObject("LDAP://RootDSE") 
  21:        strConfigNC = ObjRoot.Get("configurationNamingContext") 
  22:        WScript.DisconnectObject ObjRoot 
  23:        Set ObjRoot = Nothing 
  24: 
  25:        Set objExchangeContainer = GetObject _ 
                   ("LDAP://CN=Microsoft Exchange,CN=Services," & strConfigNC) 
  26:                                              
  27:        If Err.Number Then 
  28:           ' The Exchange container is not present. Exchange 2000 is 
                ' not installed. 
  29:           GetExchangeOrg = "" 
  30:           Exit Function 
  31:        End If 
  32: 
  33:        For Each objExchangeOrg In objExchangeContainer 
  34:            If (objExchangeOrg.Class = "msExchOrganizationContainer") Then 
  35:               Wscript.Echo "Found Exchange Organization called '" & _ 
  36:                             objExchangeOrg.Get ("cn") & "'." 
  37:               Wscript.Echo 
  38:               GetExchangeOrg = objExchangeOrg.Get ("cn") 
  39:               Exit Function 
  40:            End If 
  41:        Next 
  42: 
  43:        GetExchangeOrg = ""      
  44: 
  45:End Function
Querying Active Directory with ADSI and ADO

Querying Active Directory with scripts means using ActiveX® Data Objects (ADO) (see Figure 2). ADO relies on ADSI and an Active Directory OLE DB provider. This OLE DB provider allows you to search Active Directory. ADO establishes the connection to this provider and transmits a query command. If the retrieved data from the search must be made after the query, you must use ADSI, not ADO. The Active Directory OLE DB provider is a read-only provider.

The purpose of this document is not to focus on ADSI with ADO. The function is included here for reference purposes. It is used in Sample 10, in Sample 12, in Sample 25, and in Sample 26.

Sample 51 Searching in Active Directory 

   1:' This VBScript script uses the ADSI OLE DB interface to search Active 
    '  Directory. 
  ..: 
  13:Option Explicit 
  14: 
  15:' ------------------------------------------------------------------------ 
  16:Function ADSearch _ 
  17:         (strNamingContextSearch, strFilterSearch, _ 
               strAttribsToReturnSearch, strDepthSearch, boolEcho) 
  ..: 
  ..: 
  34:        Set objDictionary = CreateObject ("Scripting.Dictionary") 
  35: 
  36:        Set objADOConnnection = CreateObject("ADODB.Connection") 
  37:        objADOConnnection.Provider = "ADsDSOObject" 
  38:        objADOConnnection.Open "Active Directory Provider" 
  39: 
  40:        Set objCommand = CreateObject("ADODB.Command") 
  41:        Set objCommand.ActiveConnection = objADOConnnection 
  42: 
  43:        If Ucase (Mid (strNamingContextSearch, 1, 7)) = "LDAP://" Or _ 
  44:           Ucase (Mid (strNamingContextSearch, 1, 5)) = "GC://" Then 
  45: 
  46:           Set objNamingContext = GetObject(strNamingContextSearch) 
  47:        Else 
  48:           Set objRoot = GetObject("LDAP://RootDSE") 
  49:           strNamingContext = objRoot.Get(strNamingContextSearch) 
  50:           Set objRoot = Nothing 
  ..: 
  ..: 
  56:           Set objNamingContext = GetObject("LDAP://" & strNamingContext) 
  57:        End If 
  58: 
  59:        strADsPathSearch = "<" & objNamingContext.ADsPath & ">" 
  60: 
  61:        objCommand.CommandText = strADsPathSearch & ";" & _ 
  62:                                 strFilterSearch & ";" & _ 
  63:                                 strAttribsToReturnSearch & ";" & _ 
  64:                                 strDepthSearch 
  ..: 
  ..: 
  67:        If boolEcho Then WScript.Echo "Searching ..." & vbCRLF 
  68:        Set objRecordSet = objCommand.Execute 
  69: 
  70:        objDictionary.Add "RecordCount", objRecordSet.RecordCount 
  71: 
  72:        While Not objRecordSet.EOF 
  73:            For intIndice = 0 To objRecordSet.Fields.Count - 1 
  ..: 
  ..: 
  91:                ' Determine whether the returned value is multivalued. 
  92:                If IsArray (objRecordSet.Fields(intIndice).Value) Then 
  93:                   intElements = 0 
  94:                   For Each varElements In  _ 
                                  objRecordSet.Fields(intIndice).Value 
  95:                       objDictionary.Add _ 
                                  objRecordSet.Fields(intIndice).Name & ":" &  
  96:                             objRecordSet.AbsolutePosition & "/" & _ 
  97:                             intIndice & ":" & intElements, _ 
  98:                             varElements 
  99:                       intElements = intElements + 1 
 100:                   Next 
 101:                Else 
 102:                   objDictionary.Add _ 
                             objRecordSet.Fields(intIndice).Name & ":" & _ 
 103:                        objRecordSet.AbsolutePosition & "/" & intIndice, _ 
 104:                        objRecordSet.Fields(intIndice).Value 
 105:                End If 
 106:            Next 
 ...: 
 ...: 
 110:            objRecordSet.MoveNext 
 111:        Wend 
 ...: 
 ...: 
 115:        Set ADSearch = objDictionary 
 116: 
 117:        Set objNamingContext = Nothing 
 ...: 
 ...: 
 128:End Function