MAPI Tables

Everybody who ever tried to loop through a thousand messages in a folder using Outlook Object Model or CDO knows how painfully slow this can be. True, there are some tricks that can speed things up, e.g. calling Items.SetColumns() beforehand or optimizing the display algorithm (if that's what you need to do) to only retrieve the items visible to a user a couple dozen at a time. Still, neither OOM nor CDO are fast enough to make your code look blazingly fast. How come Outlook has no problem doing that?

 

The answer is MAPI tables. The general idea is that you can request an arbitrary set of properties from a set of messages in a folder in a single call, there is no need to do that one message at a time. Think of a MAPI table as a 2D array of data - each row corresponds to a single message with each column corresponding to a particular MAPI property. MAPI tables are used all over the place in Extended MAPI - contents and hierarchy tables in folders and address lists, message recipients and attachments, etc.

 

MAPI tables cannot however retrieve subobjects, such as message recipients or attachments (you can still create restrictions on recipients or attachments, see below). Another limitation in case of the Exchange provider is the size of the returned data - Exchange provider limits it to around 32 kB in a single call; that means you will never be able to retrieve message body if its size is greater than 32 kB and if you request too many rows, Exchange will return fewer rows than requested to keep the size of the returned data below 32 kB.

 

Redemption wraps IMAPITable Extended MAPI interface as a Redemption.MAPITable object. Similar to the Safe*Item objects, set MAPITable.Item property to any OOM or CDO object that exposes the RawTable property. This means that you can set the MAPITable.Item property to Items, Folders and AddressEntries in OOM and Messages, Folders, AddressEntries and Recipients objects in CDO. MAPITable is also returned by the MAPIFolder.Items.MAPITable object property.

 

MAPITable object is also exposed by the RDOStores, RDOACL, RDOItems, RDOFolders, RDOAddressLists, RDOAddressEntries, RDORecipients and RDOAttachments collections - you do not need to set the MAPITable.Item property in that case, simply use the MAPITable object returns by the MAPITable property of these collections.

 

Generally, there are two ways to use the MAPITable object:

1. Set the Columns property, specify the restriction using the MAPITable.Filter object (see examples below), then read the data using GoToFirst/GetRows methods. Use this method if you need to have an absolute control over the restriction.

2. Use the ExecSQL method, which allows to obtain a filtered and ordered set of data (returned in a standard ADODB.Recordset object) using a standard SQL query string. This is probably the easiest way to work with the MAPITable object.

 

MAPITable object


Method or property name

Arguments

Comments


Item (Object), read/write

 

This property must be set to either Items, Folders or  AddressEntries in OOM or Messages, Folders, AddressEntries and Recipients objects in CDO


RowCount (integer), read only

Returns the total number of rows in the table

 


Columns (Variant array), read/write

Use this property to set the table columns. See examples below. You can pass either a comma separated string with the OOM or DASL property names (e.g. "Subject, SenderName") or a variant array of integer property tags.

 


GoToFirst

Sets the table pointer to the first row

 


GoToLast

Sets the table pointer to the last row

 


GoTo

Index - integer

Sets the table pointer to Index

 


GetRow

Returns the current row as a variant array of variants and advances the table pointer to the next row. The properties returned are defined by the Columns property. If the row cannot be retrieved (e.g. the table pointer has reached the end of the table) Empty variant is returned instead of an array.

 


Sort Columns, Descending

 

Columns - either a comma separated string with the OOM or DASL property names (e.g. "Subject, SenderName"), an integer property tag (when sorting by a single property) or a variant array of integer property tags.

Descending  - optional. Either a boolean or a variant array of boolean values corresponding to the Columns parameter.

 

 

Sorts the table on a single or multiple properties. See example below.

         

GetRows

Count - integer

Returns the specified number of rows as a variant array. Each element of the array is another variant array of variant. The current table pointer is advanced by Count.

 


ExecSQL(SQLCommand)

 

SQLCommand - string

 

Returns an instance of the ADODB.Recordset object populated with data returned by executing the specified SQL query. See below for more information.

 


CreateBookmark

 

 

 

Returns a bookmark (integer) that can be used by the GoToBookmark method

 

FreeBookmark  Bookmark

 

Bookmark - integer

 

Frees a bookmark previously created by CreateBookmark. All bookmarks not freed by FreeBookmark will be freed when the MAPITable object is destroyed.

 

GoToBookmark  Bookmark

 

Bookmark - integer

 

Scroll the table to the position specified by the bookmark created by CreateBookmark

 

GoToNext

 

 

 

Scrolls the table to the next  row

 

GoToPrevious

 

 

 

Scrolls the table to the previous  row

 


Note that under Exchange server if you request PR_ENTRYID property, short-term entry ids will be returned from the table. This kind of entry ids can only be used in the current session, you should never persist them; otherwise short-term entry ids work just like long-term entry ids. To force MAPITable to return long-term entry ids, request the PR_LONGTERM_ENTRYID_FROM_TABLE (0x66700102) property. This property is Exchange specific, to make your code work under both Exchange and PST providers, request both PR_ENTRYID and PR_LONGTERM_ENTRYID_FROM_TABLE properties. If PR_LONGTERM_ENTRYID_FROM_TABLE is missing, fall back to PR_ENTRYID.

Also note that if a property to be returned is too big or if it is not present for a particular row, the row's array element will be a variant of type error with the error type set to the MAPI code (e.g. MAPI_E_NOT_FOUND or MAPI_E_NOT_ENOUGH_MEMORY).

 

Retrieving table data using ExecSQL method

ExecSQL is the easiest way to retrieve MAPITable data using a standard SQL style query. You do not need to know anything about the raw MAPI restrictions, sort order, etc. Just specify a SQL query as a string and read the data from a standard ADODB.Recordset object. ExecSQL will parse the SQL query to extract the search criteria (WHERE clause), columns to return (SELECT ... clause) and the sort order (ORDER BY clause):

 

set Table = CreateObject("Redemption.MAPITable")
Table.Item = Application.ActiveExplorer.CurrentFolder.Items
set Recordset = Table.ExecSQL("SELECT Subject, EntryID from Folder " & _
                                                  "where (Categories = 'one') or (Categories = 'two') " & _
                                                  "order by LastModificationTime desc")
while not Recordset.EOF
    Debug.Print(Recordset.Fields("Subject").Value & " - " & Recordset.Fields("EntryID").Value)
    Recordset.MoveNext
wend

 

ExecSQL supports the following SQL keywords and operators:

SELECT, WHERE, ORDER BY, TOP n, COUNT(*), AS, LIKE, =, <>, >, >=, <, <=, AND, OR, IN, NOT, IS NULL, IS NOT NULL, *

Other SQL keywords (such as INSERT, JOIN, GROUP BY, etc) are not supported.

If COUNT(*) is used in the SELECT clause, the returned recordset will contain a single row with a single value (column named "Count").

Column names in the SELECT, WHERE and ORDER BY clauses can be specified using either the DASL syntax (e.g. "urn:schemas:httpmail:subject" or "http://schemas.microsoft.com/mapi/proptag/0x0037001E") or using the Outlook Object Model or Redemption property name (e.g. Subject or EntryID) - see the list of the supported OOM properties below. Use OutlookSpy to figure out the DASL property names - select a message with the needed property in Outlook, click IMessage button on the OutlookSpy toolbar, select the property in the list, see the DASL edit box on the right hand side.

Important: DASL property names must be enclosed in quotes, e.g. (e.g. "urn:schemas:httpmail:subject"). OOM property names do not need to be quoted (e.g. Subject).

 

set Table = CreateObject("Redemption.MAPITable")
Table.Item = Application.ActiveExplorer.CurrentFolder.Items
Set Recordset = Table.ExecSQL("SELECT ""urn:schemas:httpmail:subject"", ""DAV:getlastmodified"" from Folder " & _
                                                   "where ""urn:schemas:httpmail:subject"" <> 'test' " & _
                                                   "order by ""DAV:getlastmodified"" desc")
while not Recordset.EOF
    Debug.Print(Recordset.Fields("""urn:schemas:httpmail:subject""").Value)
    Recordset.MoveNext
wend

 

The following Outlook Object Model / Redemption property names are supported. Properties not listed below must be specified using their MAPI property DASL syntax (use OutlookSpy).

 

ActualWork, AllDayEvent, AlternateRecipientAllowed, Anniversary, AssistantName, AssistantTelephoneNumber, Attachments, AutoForwarded, BCC, BillingInformation, Birthday, Body, Business2TelephoneNumber, BusinessAddress, BusinessAddressCity, BusinessAddressCountry, BusinessAddressPostalCode, BusinessAddressPostOfficeBox, BusinessAddressState, BusinessAddressStreet, BusinessFaxNumber, BusinessHomePage, BusinessTelephoneNumber, BusyStatus, CallbackTelephoneNumber, CardData, CarTelephoneNumber, Categories, CC, CheckSum, Children, Color, Companies, CompanyMainTelephoneNumber, CompanyName, Complete, ComputerNetworkName, ConferenceServerAllowExternal, ConferenceServerPassword, ContactAccount, ContactNames, ConversationIndex, ConversationTopic, CreationTime, CustomerID, DateCompleted, DeferredDeliveryTime, DelegationState, Delegator, DeleteAfterSubmit, Department, DLName, DueDate, Duration, Email1Address, Email1AddressType, Email1DisplayName, Email1EntryID, Email2Address, Email2AddressType, Email2DisplayName, Email2EntryID, Email3Address, Email3AddressType, Email3DisplayName, Email3EntryID, End, EndInEndTimeZone, EndUTC, EntryID, ExpiryTime, FileAs, FirstName, FlagDueBy, FlagIcon, FlagRequest, FlagStatus, FTPSite, FullName, Gender, GlobalAppointmentID, GovernmentIDNumber, HasPicture, Height, HidePaperClip, Hobby, Home2TelephoneNumber, HomeAddress, HomeAddressCity, HomeAddressCountry, HomeAddressPostalCode, HomeAddressPostOfficeBox, HomeAddressState, HomeAddressStreet, HomeFaxNumber, HomeTelephoneNumber, IMAddress, Importance, Initials, InternetCodepage, InternetFreeBusyAddress, ISDNNumber, IsOnlineMeeting, IsRecurring, IsRecurring, JobTitle, Journal, Language, LastModificationTime, LastName, Left, Location, MailingAddress, MailingAddressCity, MailingAddressCountry, MailingAddressPostalCode, MailingAddressPostOfficeBox, MailingAddressState, MailingAddressStreet, ManagerName, MeetingStatus, MeetingWorkspaceURL, MessageClass, MiddleName, Mileage, MobileTelephoneNumber, NetMeetingAutoStart, NetMeetingDocPathName, NetMeetingOrganizerAlias, NetMeetingServer, NetMeetingType, NetShowURL, NickName, NoAging, OfficeLocation, OptionalAttendees, Ordinal, OrganizationalIDNumber, Organizer, OriginatorDeliveryReportRequested, OtherAddress, OtherAddressCity, OtherAddressCountry, OtherAddressPostalCode, OtherAddressPostOfficeBox, OtherAddressState, OtherAddressStreet, OtherFaxNumber, OtherTelephoneNumber, OutlookInternalVersion, OutlookVersion, Owner, Ownership, PagerNumber, PersonalHomePage, PrimaryTelephoneNumber, Profession, RadioTelephoneNumber, ReadReceiptRequested, ReceivedByEntryID, ReceivedByName, ReceivedOnBehalfOfEntryID, ReceivedOnBehalfOfName, ReceivedTime, RecipientReassignmentProhibited, Recipients, ReferredBy, ReminderMinutesBeforeStart, ReminderOverrideDefault, ReminderPlaySound, ReminderSet, ReminderSoundFile, ReminderTime, ReplyRecipientNames, ReplyTime, RequiredAttendees, Resources, ResponseRequested, ResponseStatus, Role, SaveSentMessageFolder, SchedulePlusPriority, SelectedMailingAddress, SenderEmailAddress, SenderEmailType, SenderEntryID, SenderName, Sensitivity, Sent, SentOn, SentOnBehalfOfEmailAddress, SentOnBehalfOfEmailType, SentOnBehalfOfEntryID, SentOnBehalfOfName, Size, Spouse, Start, StartDate, StartInStartTimeZone, StartUTC, Status, StatusOnCompletionRecipients, StatusUpdateRecipients, Subject, Submitted, Suffix, TeamTask, TelexNumber, Title, To, Top, TotalWork, TTYTDDTelephoneNumber, UnRead, User1, User2, User3, User4, VotingResponse, WebPage, Width, YomiCompanyName, YomiFirstName, YomiLastName

 

Any property can be specified either by its Outlook Object Model or MAPI (DASL) name, but the returned data may be different - for the date time properties, if property is specified using the OOM name, the value will be converted from the UTC time zone (used by default by MAPI for most of the date time properties) to the local time zone. If the DASL name is used, no conversion will be performed; the value will be returned as is. Binary properties (such as PR_ENTRYID) will always be returned as a string with a hex representation of the data. Note that this this different from using Fields() or MAPIUtils.HrGetOneProp, which return binary properties as a variant array.

Note that attachments and recipients properties cannot be returned, but one can still create restrictions on these sub-properties. For attachments, Redemption will create a PR_MESSAGE_ATTACHMENTS restriction on 4 OR'ed attachment properties: PR_DISPLAY_NAME, PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACH_EXTENSION. For recipients, it will use PR_MESSAGE_RECIPIENTS restriction on 2 OR'ed recipient properties: PR_DISPLAY_NAME and PR_EMAIL_ADDRESS:

 

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
Table.ExecSQL("SELECT Subject from Folder " & _
                         "where Attachments like '%txt%' " & _
                         "order by LastModificationTime desc")
while not Recordset.EOF
    Debug.Print(Recordset.Fields("Subject").Value)
    Recordset.MoveNext
wend

 

For the named MAPI properties, either in the OOM or DASL format, Redemption needs to convert the names to the regular 4 byte MAPI properties using GetIDsFromNames. Since IMAPITable MAPI interface does not expose that method, Redemption tries to access the Parent property of the object assigned to the MAPITable.Item property (assuming it will return MAPIFolder OOM object) and checks to see if the MAPIOBJECT property is exposed. This works in Outlook 2002 or higher. In Outlook 2000 or below, MAPIFolder object in OOM does expose the MAPIOBECT property, therefore Redemption will not be able to perform the conversion and will return an error. This lmitation does not apply to the CDO 1.21 or RDO folders since they always exposed the MAPIOBJECT property.

 

Examples of accessing MAPI Tables using MAPITable object

Below are two examples that retrieve the subject of each e-mail is a folder. First one is using GetRow, second example uses GetRows() (more efficient as all rows are retrieved in a single pass). They both assume that MAPIFolder variable is initialized to an instance of Outlook's MAPIFolder object. Note that depending on the MAPI provider, GetRows() might returns an error if you request too many rows. It would be a good idea to retrieve rows in chunks of a 100 or so. If you want to open the messages later, also request PR_ENTRYID property so that you'd be able to call either Namespace.GetItemFromID() or MAPIUtils.GetItemFromID(). To convert the entry id to a string (since Redemption returns binary properties as arrays), use MAPIUtils.HrArrayToString()

 

The third examples illustrates how to retrieve a named property (corresponding to Contact.FileUnder property in OOM in this particular case). Redemption resorts to creating an instance of SafeContactItem to be able to call SafeContactItem.GetIDsFromNames() to find the property tag for the named property.

 

1. Print the subject of all messages in a folder using MAPITable.GetRow

dim Columns(0)
dim Row
set Table = CreateObject("Redemption.MAPITable")
'Table.Item = MAPIFolder.Items.Restrict("[LastModificationTime] > '05/15/2001'")
Table.Item = MAPIFolder.Items
PR_SUBJECT = &H0037001E
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

 

2. Print the subject of all messages in a folder using MAPITable.GetRows - more efficient as all rows are retrieved in a single call. 

Important Note: Exchange Server provider limits the amount of data that can be retrieved in a single call to around 32 kB, be prepared that the number of rows returned will be less than the requested number. Handle such situations by either retrieving one row at a time using GetRow (see above) or retrieving rows in chunks of 20 or 30 (depends on the amount of data returned).

dim Columns(0)
dim Row
dim Rows
set Table = CreateObject("Redemption.MAPITable")
'Table.Item = MAPIFolder.Items.Restrict("[LastModificationTime] > '05/15/2001'")
Table.Item = MAPIFolder.Items
PR_SUBJECT = &H0037001E
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
Rows = Table.GetRows(Table.RowCount)
for i = LBound(Rows) to UBound(Rows)
  Row = Rows(i)
  Debug.Print(Row(0))
next

 

3. Print the FileUnder property of all contacts and distribution lists in a folder using MAPITable.GetRows. Since we are dealing with a named property, we need to create an instance of SafeContactItem and call SafeContactItem.GetIDsFromNames to figure out the property tag for the FileUnder property

dim Columns(0)
dim Row
dim Rows
dim sItem
dim oItem
'first we need to find out the property tag for FileUnder
'it is a named property, hence we need to use GetIDsFromNames
'see http://www.cdolive.com/cdo10.htm and use OutlookSpy
'to figure out most GUIDs and ids
if MAPIFolder.Items.Count > 0 Then 'only makes sense if there are items anyway
  set oItem = MAPIFolder.Items(1) 'take any item, we only need it for GetIDsFromNames
  set sItem = CreateObject("Redemption.SafeContactItem")
  sItem.Item = oItem
  PT_STRING8 = &H1E
  PR_FILE_UNDER = sItem.GetIDsFromNames("{00062004-0000-0000-C000-000000000046}", &H8005) or PT_STRING8
  'here's the beef
  set Table = CreateObject("Redemption.MAPITable")
  Table.Item = MAPIFolder.Items
  Columns(0) = PR_FILE_UNDER
  Table.Columns = Columns
  Table.GoToFirst
  Rows = Table.GetRows(Table.RowCount)
  for i = LBound(Rows) to UBound(Rows)
    Row = Rows(i)
    Debug.Print(Row(0))
  next
End If

 

 

Restricting and searching MAPITable contents

Even though you can use MAPITable with a filtered OOM Items collection (such as returned from Items.Restrict), Redemption also implements an independent mechanism of filtering the contents of the MAPI tables if you are using the MAPITable with CDO or if you want to have greater control over restrictions.

It must be noted that using restrictions in Redemption is more work than in OOM, but gives you the same power and flexibility as if you were writing native Extended MAPI code.

Table filtering and searching in Redemption is implemented by the TableFilter object.

TableFilter object is accessible through the MAPITable.Filter object property. Just like OOM, filtering and searching using Redemption is implemented in two closely related pieces. First you need to create a restriction using the TableFilter.SetKind property (more on this below), then you can either restrict the entire collection using MAPITable.Restrict (corresponds to Items.Restrict in OOM) or search for the objects in the table satisfying the specified restriction using MAPITable.FindFirst/FindLast/FindNext methods (correspond to Items.FindFirst and Items.FindNext in OOM).

Note the difference between using Restrict and FindFirst/FindLast/FindNext methods - use Restrict if you know in advance that you will need all of the restricted objects (e.g. all unread messages in a folder); use FindFirst/FindLast/FindNext if you only need one record satisfying the restriction (e.g. you are implementing the type-ahead functionality a-la Outlook address book). Using Restrict is more expensive than FindFirst/FindLast/FindNext as the underlying MAPI implementation needs to search through the whole table as opposed to finding the first/last/Nth occurance.

Very important note: Exchange provider caches all folder contents table restrictions (approx. 1 week by default) and updates the restriction results every time there is a change in the contents table. While this can be very good news if your restriction if constant (e.g. [Unread] = true), it can literally bring your Exchange server down to its knees if you create too many restrictions that you use only once or twice. In this case instead of Restrict use  FindFirst/FindLast/FindNext  - the results are not cached.

There are essentially two kinds of restrictions in Extended MAPI: one is a restriction consisting of a single element (e.g. Subject property starts with "test") and second is a restriction consisting of one or more sub-elements (e.g. (Delivered >= #05/01/2003#) AND (Delivered < #06/01/2003#)). Restrictions that contain two or more child restrictions are RestrictionAnd and RestrictionOr. RestrictionNot can contain only one subrestriction. Note that a child subrestriction can also contain subrestrictions.

To create a new restriction using Redemption, call TableFilter.SetKind() passing an appropriate enumeration constant (see below). The method returns the specified restriction object which you can use to set the restriction properties. You can also access the restriction using the TableFilter.Restriction property. The new restriction replaces any previous restrictions. 

Restriction Kind

Numeric value

Corresponding restriction object 


RES_AND

0

RestrictionAnd

RES_BITMASK

6

RestrictionBitmask

RES_COMPAREPROPS

5

RestrictionCompareProps

RES_CONTENT

3

RestrictionContent

RES_EXIST

8

RestrictionExist

RES_NOT

2

RestrictionNot

RES_OR

1

RestrictionOr

RES_PROPERTY

4

RestrictionProperty

RES_SIZE

7

RestrictionSize

RES_SUBRESTRICTION

9

RestrictionSub

After the restriction is set, you can call either TableFilter.Restrict or TableFilter.FindFirst/FindLast/FindNext. To clear the restriction, call TableFilter.Clear. Note that calling Clear does not apply the NULL restriction (if you restricted the table using Restrict) - you will need to call Restrict again.

When in doubt how to properly construct a restriction that you need, let Outlook do the job: click "Tools | Advanced Find" in Outlook, create the restriction you need, click Find. Without closing the Advanced Find window (otherwise Outlook will delete the corresponding search folder), click IMsgStore button in OutlookSpy, click Open Root Container, go to the GetHierarchyTable tab, double click the Finder folder, in the new IMAPIFolder window go to the GetHierarchyTable tab, open the last folder. This is the search folder Outlook has just created for your search. Go to the GetSearchCriteria tab - that's the restriction tree you need to replicate in your code.


Example of using the
RES_BITMASK (RestrictionBitmask). This kind of restriction ANDs the value of the specified property (ulPropTag) with the specified mask (ulMask) and checks whether the result is 0 (relBMR = BMR_EQZ) or 1 (relBMR = BMR_NEZ) Print the subject of all unread messages in a folder. Return the messages with (PR_MESSAGE_FLAGS And MSGFLAG_READ) = 0 It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove these lines if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_BITMASK = 6
MSGFLAG_READ = 1
BMR_EQZ = 0

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionBitmask
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E
PR_MESSAGE_FLAGS = &H0E070003

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_BITMASK)
Restr.relBMR = BMR_EQZ
'means (ulPropTag And ulMask) = 0
Restr.ulPropTag = PR_MESSAGE_FLAGS
Restr.ulMask = MSGFLAG_READ
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

 

Example of the RES_COMPAREPROPS (RestrictionCompareProps). This kind of restriction compares the values of two specified properties (ulPropTag1 ans ulPropTag2) using the specified operator (Relop - RELOP_GE, RELOP_GT, RELOP_LE, RELOP_LT, RELOP_NE, RELOP_RE, RELOP_EQ)
Print the PR_SENDER_NAME and PR_SENT_REPRESENTING_NAME properties for all messages in the folder where these properties do not match (this corresponds to the messages sent of behalf of somebody else, e.g. a secretary sending on behalf of her boss). It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove these lines if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_COMPAREPROPS = 5
RELOP_NE = 5

dim Columns(1)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionCompareProps
dim Table 'As Redemption.MAPITable
dim strSender
dim strRepresenting
PR_SENDER_NAME = &H0C1A001E
PR_SENT_REPRESENTING_NAME = &H0042001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_COMPAREPROPS)
Restr.Relop = RELOP_NE
Restr.ulPropTag1 = PR_SENDER_NAME
Restr.ulPropTag2 = PR_SENT_REPRESENTING_NAME
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SENDER_NAME
Columns(1) = PR_SENT_REPRESENTING_NAME
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    if TypeName(Row(0)) = "String" Then
      strSender = Row(0)
    Else
      strSender = "N/A"
    End If
    if TypeName(Row(1)) = "String" Then
      strRepresenting = Row(1)
    Else
      strRepresenting = "N/A"
    End If
    Debug.Print("Sender: " & strSender & ", Representing: " & strRepresenting)
  End If
Loop Until IsEmpty(Row)

 

Example of the RES_CONTENT (RestrictionContent). This restriction matches the specified property (ulPropTag) with the search string (lpProp). using the logic defined by ulFuzzyLevel (FL_FULLSTRING, FL_PREFIX, FL_SUBSTRING). These 3 constants can be "or"ed with the following modifiers: FL_IGNORECASE, FL_IGNORENONSPACE, FL_LOOSE. Note that this restriction can only be used with the string properties.
Print the subject of all messages in the folder with the subject containing "redemption". It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove these lines if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_CONTENT = 3
FL_SUBSTRING = 1
FL_IGNORECASE = &H10000

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionContent
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_CONTENT)
Restr.ulFuzzyLevel = FL_SUBSTRING or FL_IGNORECASE
Restr.ulPropTag = PR_SUBJECT
Restr.lpProp = "redemption"
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

 

Example of the RES_EXIST (RestrictionExist). This restriction checks if the specified property exists on e message Print the subject of all messages in the folder that have PR_TRANSPORT_MESSAGE_HEADERS property. This property is usually only present on the messages received from the internet. Messages received from Exchange users in the same domain usually do not have this property. It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove this line if Redemption (SafeOutlook) type library
'has been added to the project references list

RES_EXIST = 8

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionExist
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E
PR_TRANSPORT_MESSAGE_HEADERS = &H007D001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_EXIST)
Restr.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

 

Example of the RES_PROPERTY (RestrictionProperty). This restriction compares the property specified by ulPropTag with the given value (lpProp) using the operator defined by relop (RELOP_GE, RELOP_GT, RELOP_LE, RELOP_LT, RELOP_NE, RELOP_RE, RELOP_EQ). Print the subject of all messages in the folder with an attachment - check if PR_HASATTACH is true It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove this line if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_PROPERTY = 4
RELOP_EQ = 4

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionProperty
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E
PR_HASATTACH = &H0E1B000B

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_PROPERTY)
Restr.ulPropTag = PR_HASATTACH
Restr.Relop = RELOP_EQ
Restr.lpProp = true
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

 

Example of the RES_SIZE (RestrictionSize). This restriction compares the size of the given property (ulPropTag) with the value specified by cb using the operator given by relop (RELOP_GE, RELOP_GT, RELOP_LE, RELOP_LT, RELOP_NE, RELOP_RE, RELOP_EQ). Print the subject of all messages in the folder with the PR_BODY size in excess of 50 kB. It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove this line if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_SIZE = 7
RELOP_GE = 3

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionSize
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E
PR_BODY = &H1000001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_SIZE)
Restr.ulPropTag = PR_BODY
Restr.Relop = RELOP_GE
Restr.cb = 50*1024
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

 
Example of the
RES_SUBRESTRICTION (RestrictionSub). This restriction allows to set a restriction on one of the message's subobjects, such as a recipient or an attachment (ulSubObject can only be PR_MESSAGE_ATTACHMENTS or PR_MESSAGE_RECIPIENTS).
Print the subject of all messages in the folder where an attachment has a ".txt" substring in its name. One can search on the PR_ATTACH_EXTENSION property being ".txt" (FL_FULLSTRING + FL_IGNORECASE), however PR_ATTACH_EXTENSION is not always present, so let's search on ".txt" substring (FL_SUBSTRING + FL_IGNORECASE) in PR_ATTACH_FILENAME. It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object

'remove these lines if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_SUBRESTRICTION = 9
RES_CONTENT = 3
PR_ATTACH_FILENAME = &H3704001E
PR_MESSAGE_ATTACHMENTS = &H0E13000D
FL_SUBSTRING = 1
FL_IGNORECASE = &H10000

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionSub
dim RestrAtt 'As Redemption.RestrictionSub
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_SUBRESTRICTION)
Restr.ulSubObject = PR_MESSAGE_ATTACHMENTS
set RestrAtt = Restr.SetKind(RES_CONTENT)
RestrAtt.ulFuzzyLevel = FL_SUBSTRING or FL_IGNORECASE
RestrAtt.ulPropTag = PR_ATTACH_FILENAME
RestrAtt.lpProp = ".txt"
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

Example of the RES_NOT (RestrictionNot). This restriction negates the restriction set as it subrestriction. Similar to the RES_EXIST sample, the sampe above
Prints the subject of all messages in the folder that do *not* have the PR_TRANSPORT_MESSAGE_HEADERS property. This property is usually only present on the messages received from the internet. Messages received from Exchange users in the same domain usually do not have this property.
It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object

'remove this line if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_NOT = 2
RES_EXIST = 8

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionNot
dim Restr2 'As Redemption.RestrictionExist
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E
PR_TRANSPORT_MESSAGE_HEADERS = &H007D001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_NOT)
set Restr2 = Restr.SetKind(RES_EXIST)
Restr2.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

Example of the RES_AND (RestrictionAnd). This restriction applies the AND logical operator to its child restrictions RES_OR (RestrictionOr) is completely analogous, it just uses the OR operator.
The example prints the subject of all messages satisfying the condition (PR_MESSAGE_DELIVERY_TIME >= Feb. 01, 2003) AND (PR_MESSAGE_DELIVERY_TIME < Mar. 01, 2003). It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object.

'remove this line if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_AND = 0
RES_PROPERTY = 4
RELOP_GE = 3
RELOP_LT = 0

dim Columns(0)
dim Row
dim Filter
'As Redemption.TableFilter
dim RestrAnd 'As Redemption.RestrictionAnd
dim Restr1 'As Redemption.RestrictionProperty
dim Restr2 'As Redemption.RestrictionProperty
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E
PR_MESSAGE_DELIVERY_TIME = &H0E060040

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_AND)
set Restr1 = Restr.Add(RES_PROPERTY)
Restr1.relop = RELOP_GE
Restr1.ulPropTag = PR_MESSAGE_DELIVERY_TIME
Restr1.lpProp = #02/01/2003#
set Restr2 = Restr.Add(RES_PROPERTY)
Restr2.relop = RELOP_LT
Restr2.ulPropTag = PR_MESSAGE_DELIVERY_TIME
Restr2.lpProp = #03/01/2003#
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

Restricting address book container entries

Restrict the "Contacts" address book container for the entries that have a "dmitry" prefix. This is the only kind of restriction supported by most address book providers unfortunately :-(
This restriction is used when resolving a name manually typed by a user, so the AB providers don't bother implementing any other restrictions, the only exception being Exchange GAL.

'remove these lines if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_PROPERTY = 4
RELOP_EQ = 4

dim Columns(1)
dim Row
dim Filter
'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionProperty
dim Table 'As Redemption.MAPITable
PR_DISPLAY_NAME = &H3001001E
PR_EMAIL_ADDRESS = &H3003001E
PR_ANR = &H360C001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = Application.Session.AddressLists.Item("Contacts").AddressEntries
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_PROPERTY)
Restr.ulPropTag = PR_ANR
Restr.Relop = RELOP_EQ
Restr.lpProp = "dmitry"
Filter.Restrict
'restriction is done, read the data
Columns(0) = PR_DISPLAY_NAME
Columns(1) = PR_EMAIL_ADDRESS
Table.Columns = Columns
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0) & vbTab & Row(1))
  End If
Loop Until IsEmpty(Row)

Using TableFilter.FindFirst/FindNext/FindLast methods

  Example of calling FindFirst/FindNext/FindLast using a specified restriction.
  Display the subject of all unread messages in a folder in a loop until there aren't any left.
  It is assumed that MAPIFolder variable points to an OOM's MAPIFolder object

'remove these lines if Redemption (SafeOutlook) type library
'has been added to the project references list
RES_BITMASK = 6
MSGFLAG_READ = 1
BMR_EQZ = 0
PR_MESSAGE_FLAGS = &H0E070003

dim Columns(0)
dim Row
dim Filter 'As Redemption.TableFilter
dim Restr 'As Redemption.RestrictionBitmask
dim Table 'As Redemption.MAPITable
PR_SUBJECT = &H0037001E

set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
'set up the restriction
set Filter = Table.Filter
Filter.Clear
set Restr = Filter.SetKind(RES_BITMASK)
Restr.relBMR = BMR_EQZ 'means (ulPropTag And ulMask) = 0
Restr.ulPropTag = PR_MESSAGE_FLAGS
Restr.ulMask = MSGFLAG_READ
Columns(0) = PR_SUBJECT
Table.Columns = Columns
if Filter.FindFirst(TRUE) Then
  do
    Row = Table.GetRow
    if Not IsEmpty(Row) Then
      Debug.Print(Row(0))
    End If
  Loop Until not Filter.FindNext(TRUE)
End If

 

Sorting the tables

Sort capabilities in Redemption are similar to those in Outlook Object Model, with one exception: you can sort on multiple properties at the same time.

To sort the items in the Redemption.MAPITable object, call MAPITable.Sort passing either an integer property tag or a variant array of integer property tags. You can also pass a comma separated string of the OOM or DASL property names (e.g. "Subject, SenderName") - see the example #3 below.

The second (Descending, optional) parameter is either a boolean specifying the sort order (true for descending or false for ascending) or a variant array of boolean values specifying the sort order for each of the property tags passed in the first argument.

Two examples are given below. One sorts the table by a single property PR_MESSAGE_DELIVERY_TIME, the second example sorts the table first by PR_SUBJECT, then by PR_MESSAGE_DELIVERY_TIME.

1. Sort the table on one property - PR_MESSAGE_DELIVERY_TIME

'Print the subject of all messages in a folder
dim Columns(0)
dim Row
dim strSubject
set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
PR_SUBJECT = &H0037001E
PR_MESSAGE_DELIVERY_TIME = &H0E060040
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.Sort PR_MESSAGE_DELIVERY_TIME, False
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

2. Sort the table on 2 properties - first on PR_SUBJECT, then on PR_MESSAGE_DELIVERY_TIME

dim Columns(0)
dim Row
dim SortTags(1)
dim SortOrders(1)
set Table = CreateObject("Redemption.MAPITable")
Table.Item = MAPIFolder.Items
PR_SUBJECT = &H0037001E
PR_MESSAGE_DELIVERY_TIME = &H0E060040
SortTags(0) = PR_SUBJECT
SortTags(1) = PR_MESSAGE_DELIVERY_TIME
SortOrders(0) = True
SortOrders(1) = False
Columns(0) = PR_SUBJECT
Table.Columns = Columns
Table.Sort SortTags, SortOrders
Table.GoToFirst
do
  Row = Table.GetRow
  if Not IsEmpty(Row) Then
    Debug.Print(Row(0))
  End If
Loop Until IsEmpty(Row)

3. Sort the table specifying the columns as a comma separated string

set Session = CreateObject("Redemption.RDOSession")
Session.Logon
set Folder = Session.GetDefaultFolder(olFolderInbox)
set Items = Folder.Items
Items.MAPITable.Columns = "Subject, SenderName"
Items.MAPITable.Sort "ReceivedTime", false
for each Item in Items
  Debug.Print Item.Subject & " - " & Item.SenderName
next