Programming Microsoft Outlook and Microsoft Exchange, Second Edition (DV-MPS Programming)
Exchange Server includes excellent facilities to run rules on the server, which fire depending on the conditions you set. If you've looked at the Rules Wizard in Outlook, you have a sense of the complex rules you can create on your Exchange server. In the past, rules could only be created programmatically with C/C++, but with the Rules COM component, Microsoft Visual Basic developers can create complex rules for their application folders in Exchange Server. The Rules COM component provides an extensive object library, which we'll look at later.
Storing Rules
Before discussing the Rules component, we first must take a look at how rules are stored in the Exchange Server system. The Exchange Server system stores rules as hidden messages inside folders. To find rules in the CDO HiddenMessages collection for a folder, search for the message class IPM.Rule.Message. This message class specifies that the item is a rule item, and the properties on the item contain the various conditions for the rule. The easiest way to see the different rules is to use the MDB Viewer Test Application (MDBVUE) tool included on the companion CD. This tool allows you to see hidden messages in folders as well as retrieve the properties of all items in the Exchange Server store. Figure 16-1 shows a screen from the MDB Viewer Test Application.
Figure 16-1. The MDB Viewer Test Application provides functionality to investigate the objects stored in your Exchange Server system. This tool can even be used to investigate the properties on rule items stored as hidden messages in your folders.
Creating an Instance of the Rules Component
To create an instance of this component, all you need to do is pass the ProgID for the component, MSExchange.Rules, to the CreateObject method, as shown in the following line of code. The Rules component contains other instantiable objects, which you will see later in this chapter, that correspond to conditions you can set for the component.
Set myRules = CreateObject("MSExchange.Rules") |
Using the Rules Component
The easiest way to show you how to use the Rules component is to step through some snippets of code. These snippets show you many of the objects and methods that constitute the component. The first sample we'll review, written in Visual Basic, illustrates the major issues you'll confront when using the Rules component to create rules that compare a single property to a specified value. The sample creates a new rule, named Imp Rule, that looks for low-importance messages sent to the Inbox. When the rule finds a low-importance message in the Inbox, it moves it to a subfolder of the Inbox named To Me.
Dim oSession As MAPI.Session Set oSession = CreateObject("MAPI.Session") oSession.Logon Set oRules = CreateObject("MSExchange.Rules") 'This can also be Public Folders. 'You need Owner permissions on the folder to create 'and enable a rule. oRules.Folder = oSession.Inbox 'Set the property value for a condition in the rule 'to the importance of the message Set ImpProp = CreateObject("MSExchange.PropertyValue") ImpProp.Tag = CdoPR_IMPORTANCE ImpProp.Value = 0 'Set a condition so that this rule 'looks for all nonimportant messages Set ImpCond = CreateObject("MSExchange.PropertyCondition") ImpCond.Value = ImpProp ImpCond.PropertyTag = CdoPR_IMPORTANCE ImpCond.Operator = 7 Set oFolder = oSession.Inbox.Folders("To Me") 'Set the action for the rule Set oAction = CreateObject("MSExchange.Action") oAction.ActionType = 1 oAction.Arg = oFolder 'Create the actual rule Set oRule = CreateObject("MSExchange.Rule") oRule.Name = "Imp Rule" oRule.Actions.Add 1, oAction oRule.Condition = ImpCond 'Add it to the Rules collection oRules.Add , oRule oRules.Update oSession.Logoff |
When using the Rules component, you need to make sure you have a valid CDO session with the server so that you can pass in a CDO Folder object to the Folder property on the Rules component, which tells the Rules component which folder to create your rules in.
Your next step is to set up conditions that must be met by the incoming message to make the rule fire. The Rules component supports many similar types of conditions. We'll cover the major ones you'll use: Bitmask, Comment, CompareProps, Content, Exists, Logical, Property, Size, and Sub. We'll look in detail at Bitmask, Content, Logical, and Property.
In the preceding code example, you saw a property condition in action. To create a PropertyCondition object, the code calls the CreateObject method with the ProgID for a property condition. After you create the condition object, you have to set its properties. For the PropertyCondition object, you need to set the Value, PropertyTag, and Operator properties.
For the Value property, you have to pass a PropertyValue object that contains the value you want to compare to the desired property. For this reason, before a PropertyCondition object is created, a PropertyValue object is created, with its Tag property set to the CDO property identifier that we're interested in, CdoPR_IMPORTANCE. The Value property for the object is set to the value we want satisfied by the condition. In this case, the Value property is set to 0, which specifies a low-importance message in CDO.
NOTE
When I tested the code for the Rules component on different machines, I sometimes had trouble getting the importance and sensitivity properties to be identified. If you have trouble getting your rule to fire, try testing your rule with different message properties.
Now that we have a valid PropertyValue object, we can pass it to the Value property for the PropertyCondition object. Then we need to set the PropertyTag property on our condition. This property should contain the same property identifier as the PropertyValue object.
Next, the Operator property must be set. The Operator property can have seven possible values, described in Table 16-1.
Table 16-1. Values for the Operator property.
Name | Value | Description |
---|---|---|
REL_GE | 1 | Greater than or equal to |
REL_GT | 2 | Greater than |
REL_LE | 3 | Less than or equal to |
REL_LT | 4 | Less than |
REL_NE | 5 | Not equal to |
REL_RE | 6 | Like |
REL_EQ | 7 | Equal to |
We want the importance level for the Operator property to equal the value specified earlier for the PropertyValue object. The code sets the Operator value to 7, which is the EQUALTO operator.
The code's next step is to create the Action object, which contains the action that Exchange Server should take if the property condition equals the property value we specified. The Action object has two properties we need to set: ActionType and Arg. ActionType specifies the type of action to take on the item. For example, you can automatically delete the item, move the item to a different folder, or bounce the item back to the person who submitted it. Table 16-2 shows you the possible values for the ActionType property.
Table 16-2. Values for the ActionType property.
Name | Value | Description |
---|---|---|
ACTION_MOVE | 1 | Move the message to the folder object specified in Arg. |
ACTION_COPY | 2 | Copy the message to the folder object specified in Arg. |
ACTION_DELETE | 3 | Delete the message. |
ACTION_REPLY | 4 | Respond to the message with the message specified in Arg. |
ACTION_OOFREPLY | 5 | Respond to the message with the Out-of-office message specified in Arg. |
ACTION_FORWARD | 6 | Forward the message to the recipient list specified in Arg. |
ACTION_DELEGATE | 7 | Delegate the message to the recipient list specified in Arg. |
ACTION_BOUNCE | 8 | Return the message to the sender for the reason specified in Arg. |
ACTION_TAG | 9 | Tag the message to set the property specified in Arg. |
ACTION_MARKREAD | 10 | Mark as read. |
ACTION_DEFER | 11 | Defer action. |
In the code, the ActionType property was set to 1, which moves the item to the folder we specify in the Arg property. Depending on the value specified for ActionType, you might need to set the Arg property to a value. If you pick the delete action, you do not have to specify the Arg property on your Action object.
As you can see, the Arg property and the Action property work together to create the action for your rule. In our example, the code finds the specific folder we're moving items to by using CDO, and then it sets the Action object's Arg property to the CDO Folder object. If we do not specify this folder, the rule will not work.
After we have a condition and an action for the rule, we need to create the actual rule by creating a Rule object. The Rule object has a large number of properties that you can use, but we'll look at only a subset of them—Actions, Name, and Condition.
The Name property contains the friendly name for the rule. The Action property returns the Actions collection, which contains all the Action objects for the rule. We add a new rule to the collection by using the Add method on the collection. The Add method takes two parameters, the first being the position in the collection where the Action object should be placed. You can have multiple actions for a rule such as a forward action and a reply action. The second parameter for the Add method is the Action object you want to add to the collection.
The final property we need to set on the Rule object to successfully create the object is the Condition property. The Condition property should be set to the condition object we created for the rule. As you will see later, you can have multiple conditions for a rule, but there is a catch—you need to link all the conditions together using another type of object, the LogicalCondition object.
Now that the properties for the new Rule object are set, all we need to do is add the object to the Rules collection. To do this, we pass our Rule object to the Add method on the Rules collection. The first parameter of this method, which is blank in the code, is an optional integer that specifies the position before the insertion point for the new rule. Since this parameter is not specified, the new rule is inserted at the end of the collection. If you do specify a position for this property, you must update the indices for the Rules object by calling the UpdateIndices method and then the Update method on the object.
The second parameter of the Add method is the object that contains the new Rule object you want to add. We already created and set the properties of this object in the code, so that's it! We just created a new rule. Now we're going to look at some of the other condition objects we can use to set more complex conditions for our rules. Because a lot of the steps are similar for these other types of conditions, I'm going to highlight only the steps that differ and are required for each condition type.
Specifying a Logical Condition
Most of the time, when you create rules, you will not use only one property as your condition. You'll have multiple conditions, such as specifying only those messages that are of low importance and sent directly to you. To create multiple conditions, you need to use the LogicalCondition object in conjunction with the other condition objects. The next example shows how a LogicalCondition object is used in conjunction with two PropertyCondition objects to create a rule that checks to see whether messages are of low importance and sent directly to you. If the rule finds a message that meets these conditions, the message is moved to the To Me subfolder of the Inbox.
Dim oSession As MAPI.Session Set oSession = CreateObject("MAPI.Session") oSession.Logon Set oRules = CreateObject("MSExchange.Rules") 'This can also be Public Folders. 'You need Owner permissions on the folder to create 'and enable a rule. oRules.Folder = oSession.Inbox 'Set the property value for a condition in the rule 'to the importance of the message Set ImpProp = CreateObject("MSExchange.PropertyValue") ImpProp.Tag = CdoPR_IMPORTANCE ImpProp.Value = 0 'Set a condition so that this rule 'looks for all nonimportant messages Set ImpCond = CreateObject("MSExchange.PropertyCondition") ImpCond.Value = ImpProp ImpCond.PropertyTag = CdoPR_IMPORTANCE ImpCond.Operator = 7 'Set a property value for messages sent to me Set MeProp = CreateObject("MSExchange.PropertyValue") MeProp.Tag = CdoPR_MESSAGE_TO_ME MeProp.Value = True 'Set a condition so that this rule 'looks for all messages sent to me Set MeCond = CreateObject("MSExchange.PropertyCondition") MeCond.Value = MeProp MeCond.PropertyTag = CdoPR_MESSAGE_TO_ME MeCond.Operator = 7 Set LogCond = CreateObject("MSExchange.LogicalCondition") LogCond.Operator = 1 LogCond.Add ImpCond LogCond.Add MeCond Set oFolder = oSession.Inbox.Folders("To Me") 'Set the action for the Rule object Set oAction = CreateObject("MSExchange.Action") oAction.ActionType = 1 oAction.Arg = oFolder 'Create the actual rule Set oRule = CreateObject("MSExchange.Rule") oRule.Name = "Imp Rule" oRule.Actions.Add 1, oAction oRule.Condition = LogCond 'Add it to the Rules collection oRules.Add , oRule oRules.Update oSession.Logoff |
In the code, two PropertyCondition objects and two PropertyValue objects are created to specify the conditions for the rule. To link the two conditions, the code creates a LogicalCondition object. The LogicalCondition object is actually a collection of other condition objects from which you can add or delete objects.
To add the two conditions to the LogicalCondition object, the code uses the Add method of the LogicalCondition object. The Add method takes a Condition object as its parameter and will add the object to the collection. Once all the condition objects are added to the collection, the logic that links the two or more conditions must be set. To do this, we use the Operator property on the LogicalCondition object. This property has three possible values: L_AND (1), L_OR (2), and L_NOT (3). Since we want all messages sent directly to the person and messages of low importance to be the only messages moved to the folder, we set the Operator property on the LogicalCondition object to be 1, or L_AND, which causes Exchange Server to fire the rule only if both conditions are met on the item.
Searching for Specific Content
The Rules component provides the ContentCondition object so that you can search for specific content. This object allows you to search for specific text in either the message body or the message subject. You can use this searching capability to fire off rules that perform specific actions. For example, you can use the ContentCondition object to create a simple profanity filter for a discussion application. If any offensive words are placed into the message body or message subject, you can automatically delete the message or move it to a folder for an administrator to look at.
To show you how to use the ContentCondition object, the code example we have been working with has been updated to search the message body of incoming items for the phrase New Policy. Now our rule fires only when an item is of low importance, sent directly to the person, and contains the phrase New Policy in the message body. If the rule finds a message that meets these conditions, the message is moved to the To Me subfolder of the Inbox. Here is the code that creates this rule:
Dim oSession As MAPI.Session Set oSession = CreateObject("MAPI.Session") oSession.Logon Set oRules = CreateObject("MSExchange.Rules") 'This can also be Public Folders. 'You need Owner permissions on the folder to create 'and enable a rule. oRules.Folder = oSession.Inbox 'Set the property value for a condition in the rule 'to the importance of the message Set ImpProp = CreateObject("MSExchange.PropertyValue") ImpProp.Tag = CdoPR_IMPORTANCE ImpProp.Value = 0 'Set a condition so that this rule 'looks for all nonimportant messages Set ImpCond = CreateObject("MSExchange.PropertyCondition") ImpCond.Value = ImpProp ImpCond.PropertyTag = CdoPR_IMPORTANCE ImpCond.Operator = 7 'Set a property value for messages sent to me Set MeProp = CreateObject("MSExchange.PropertyValue") MeProp.Tag = CdoPR_MESSAGE_TO_ME MeProp.Value = True 'Set a condition so that this rule 'looks for all messages sent to me Set MeCond = CreateObject("MSExchange.PropertyCondition") MeCond.Value = MeProp MeCond.PropertyTag = CdoPR_MESSAGE_TO_ME MeCond.Operator = 7 'Set a property value for messages with New Policy Set ContProp = CreateObject("MSExchange.PropertyValue") ContProp.Tag = CdoPR_BODY ContProp.Value = "New Policy" 'Set a condition so that this rule 'looks for all with the New Policy keywords Set ContCond = CreateObject("MSExchange.ContentCondition") ContCond.Value = ContProp ContCond.PropertyType = CdoPR_BODY ContCond.Operator = 1 'Substring Set LogCond = CreateObject("MSExchange.LogicalCondition") LogCond.Operator = 1 LogCond.Add ImpCond LogCond.Add MeCond LogCond.Add ContCond Set oFolder = oSession.Inbox.Folders("To Me") 'Set the action for the Rule object Set oAction = CreateObject("MSExchange.Action") oAction.ActionType = 1 oAction.Arg = oFolder 'Create the actual rule Set oRule = CreateObject("MSExchange.Rule") oRule.Name = "Imp Rule" oRule.Actions.Add 1, oAction oRule.Condition = LogCond 'Add it to the Rules collection oRules.Add , oRule oRules.Update oSession.Logoff |
As you can see from the code, to successfully create a ContentCondition object, you must perform two steps. First you must create a PropertyValue object and fill in its properties with the CDO property you're interested in searching, and then you must fill in three properties on the ContentCondition object—Value, PropertyType, and Operator. Set the Value property to contain the PropertyValue object that you create. The Value property tells the ContentCondition object the value for which the rule should search in incoming messages. Set the PropertyType property to the same CDO property set for the Value property on the PropertyValue object. PropertyType tells the ContentCondition object which CDO property to search in for the value. The Operator property contains a hex value that specifies the type of search to perform for the specified value. This search can be an exact match, a substring, or a prefix. You can have only one of these three types of searches. Table 16-3 shows the settings for the Operator property. However, you can combine the last three settings in the table—IGNORECASE, IGNORENONSPACE, and LOOSE—with the search type. For example, to specify a search that looks for a substring and ignores cases and nonspaces, you would set the Operator property to &H30001.
Table 16-3. Values for the Operator property.
Name | Hex Value | Description |
---|---|---|
FULLSTRING | 0 | Full string |
SUBSTRING | 1 | Substring |
PREFIX | 2 | Prefix |
IGNORECASE | 10000 | Ignore case |
IGNORENONSPACE | 20000 | Ignore nonspace |
LOOSE | 40000 | Ignore high bits (maps Unicode to corresponding ANSI values) |
Searching for a Particular Bitmask
Sometimes you'll want to retrieve a particular property on a message—most commonly, the CdoPR_MESSAGE_FLAGS property—to see if the property meets a certain criterion. The CdoPR_MESSAGE_FLAGS property contains a bitmask that describes whether the message has attachments or was sent from the Internet. The following example adds to the rule we've been creating a condition that searches incoming messages to determine whether they have attachments:
Dim oSession As MAPI.Session Set oSession = CreateObject("MAPI.Session") oSession.Logon Set oRules = CreateObject("MSExchange.Rules") 'This can also be Public Folders. 'You need Owner permissions on the folder to create 'and enable a rule. oRules.Folder = oSession.Inbox 'Set the property value for a condition in the rule 'to the importance of the message Set ImpProp = CreateObject("MSExchange.PropertyValue") ImpProp.Tag = CdoPR_IMPORTANCE ImpProp.Value = 0 'Set a condition so that this rule 'looks for all nonimportant messages Set ImpCond = CreateObject("MSExchange.PropertyCondition") ImpCond.Value = ImpProp ImpCond.PropertyTag = CdoPR_IMPORTANCE ImpCond.Operator = 7 'Set a property value for messages sent to me Set MeProp = CreateObject("MSExchange.PropertyValue") MeProp.Tag = CdoPR_MESSAGE_TO_ME MeProp.Value = True 'Set a condition so that this rule 'looks for all messages sent to me Set MeCond = CreateObject("MSExchange.PropertyCondition") MeCond.Value = MeProp MeCond.PropertyTag = CdoPR_MESSAGE_TO_ME MeCond.Operator = 7 'Set a property value for messages with New Policy Set ContProp = CreateObject("MSExchange.PropertyValue") ContProp.Tag = CdoPR_BODY ContProp.Value = "New Policy" 'Set a condition so that this rule 'looks for all with the New Policy keywords Set ContCond = CreateObject("MSExchange.ContentCondition") ContCond.Value = ContProp ContCond.PropertyType = CdoPR_BODY ContCond.Operator = 1 'Substring 'Create a condition for messages with attachments Set BitCond = CreateObject("MSExchange.BitmaskCondition") BitCond.Value = 16 BitCond.PropertyTag = CdoPR_MESSAGE_FLAGS BitCond.Operator = 2 Set LogCond = CreateObject("MSExchange.LogicalCondition") LogCond.Operator = 1 LogCond.Add ImpCond LogCond.Add MeCond LogCond.Add ContCond LogCond.Add BitCond Set oFolder = oSession.Inbox.Folders("To Me") 'Set the action for the Rule object Set oAction = CreateObject("MSExchange.Action") oAction.ActionType = 1 oAction.Arg = oFolder 'Create the actual rule Set oRule = CreateObject("MSExchange.Rule") oRule.Name = "Imp Rule" oRule.Actions.Add 1, oAction oRule.Condition = LogCond 'Add it to the Rules collection oRules.Add , oRule oRules.Update oSession.Logoff |
As you can see in the code, when creating a bitmask condition, you need to set three properties on the object: Value, PropertyTag, and Operator. The Value property takes the value with which the PropertyTag value is masked. In this example, we placed the value 16 in the Value property to create a rule that looks for attachments. The PropertyTag value specifies the CDO property that you want to mask with the Value property. In this example, we used the CdoPR_MESSAGE_FLAGS property. The Operator property specifies the bitmask operator for the property and can take one of two values: B_EQZ (1) or B_NEZ (2). B_EQZ creates a bitmask and checks to see whether the returned value is zero. B_NEZ creates a bitmask and checks to see whether the returned value is nonzero. In this example, we created the bitmask and checked to see whether the value was nonzero.
In this section, we've seen some of what the Rules component can do. In the section titled "Project Application," we'll see how to create rules that fire on all incoming messages. The Rules component has other conditions that you can set and other capabilities—it doesn't just create rules but also reads and modifies existing rules. To learn more about these other capabilities, refer to the Exchserv.chm file on the companion CD.