Chapter 11: Searching Outlook Data

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

This article is an excerpt from Programming Applications for Microsoft Office Outlook 2007 by Randy Byrne and Ryan Gregg, from Microsoft Press (ISBN 9780735622494, copyright Microsoft Press 2007, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Searching provides relief from information overload. It can also help you build a solution that locates items in folders and stores, and helps end users discover their data and become more productive.

After reading this chapter, you should have a good understanding of the following topics:

  • Overview of programmatic search

  • Microsoft Office Outlook 2007 query languages

  • Entry points for search in the Outlook object model

Contents

  • Overview of Searching Data

  • Outlook Query Languages

  • Date-Time Comparisons

  • Integer Comparisons

  • Invalid Properties

  • Comparison and Logical Operators

  • Search Entry Points

  • Search Considerations

  • Summary

  • Additional Resources

Overview of Searching Data

Instant Search is one of the premiere features of Outlook 2007. In the past, search was difficult and non-performant both in the Outlook user interface and programmatically. That story has changed with Outlook 2007. As a developer, you can participate in search in a first-class manner by issuing queries that return results based on the content indexing engine that supports Instant Search. Once your query has been processed, the results can be returned in a variety of objects including the Table object, the Items collection, and the Search object. You can also write code that uses the Advanced Query Syntax (AQS) offered by Microsoft Windows Desktop Search to drive the Instant Search pane, shown in Figure 11-1.

Figure 11-1. Instant Search pane

Instant Search pane

Outlook Query Languages

Outlook 2007 supports three different query languages, each of which has appropriate scenarios and entry points. For programmatic search, there are also several different ways of returning results from a search. Before learning how to implement programmatic search, it’s important for you to understand the query languages and their appropriate usage. The available query languages in Outlook 2007 are listed in Table 11-1.

Table 11-1. Outlook 2007 query languages

Query language

Description

AQS

AQS is used by Windows Desktop Search and is the query language for the Instant Search feature in Outlook 2007.

DASL

DAV Searching and Locating (DASL) query language is based on the Microsoft Exchange Server implementation of DASL in Outlook. DASL has been used in several versions of Outlook. In Outlook 2007, DASL has new prominence because it can be used to return results in the new Table object. DAV is an abbreviated version of Web Distributed Authoring and Versioning (WebDAV).

Jet

Jet query language, based on the Microsoft Jet Expression Service, has been used in several versions of Outlook to provide a simple query language. Note that the Jet query language has the same syntax as that supported by the Microsoft Jet Expression Service, hence the name Jet query language. Jet is used to create filter strings for the Restrict method of the Items and Table objects.

Each query language has one or more entry points in the Outlook object model. Jet is the simplest to use, but it also is less powerful than DASL or AQS. AQS is simple to use but extremely powerful. However, its use is limited to the Search method of the Explorer object. It cannot be used to provide a restriction for the Table or Items objects. Results returned by an AQS query can be displayed only in the Outlook user interface. DASL is a difficult query language to write and to master, but it does have significant power for driving both the Table object and Items collection. For example, the following code sample displays all items that contain the exact phrase "Office 2007" in the subject and that have been received in the last month. The scope of the query is all mail folders in all stores where indexing is enabled. It then displays the results of the search in a separate Explorer window.

private void DemoInstantSearch()
{
    if (Application.Session.DefaultStore.IsInstantSearchEnabled)
    {
        Outlook.Explorer explorer = Application.Explorers.Add(
            Application.Session.GetDefaultFolder(
            Outlook.OlDefaultFolders.olFolderInbox)
            as Outlook.Folder,
            Outlook.OlFolderDisplayMode.olFolderDisplayNormal);
        string filter = "subject:" +
            "\"" + "Office 2007" + "\"" +
            " received:(last month)";
        explorer.Search(filter,
            Outlook.OlSearchScope.olSearchScopeAllFolders);
        explorer.Display();
    }
}

AQS

AQS is the query language for Windows Desktop Search. For an in-depth discussion of all aspects of Windows Desktop Search, search MSDN. AQS queries cannot be submitted directly to methods such as Table.Restrict or Items.Restrict. You can supply an AQS query only to the Search method of the Explorer object. When you call this method, search results are displayed in the Explorer window. You can call the Search method on multiple Explorer objects, and each will display separate search results. Outlook uses AQS to return search results.

The following discussion concentrates on AQS keywords that you can use to cause the Search method of the Explorer object to display results in an Explorer window. Some of the keywords are specific to a module such as Mail or Calendar, whereas other keywords are appropriate to all modules.

Case Sensitivity

Keywords and search specifiers for AQS are case-insensitive. When results are returned, they will also be case-insensitive. For example, subject:office or SUBJECT:OFFICE will both return items that contain "Office," "office," or "OFFICE" in the subject.

Keywords and Locale

Keywords for AQS queries can be submitted based on the locale for the installed Windows operating system. However, to make your life as a developer easier, you can submit the English keywords shown in Tables 11-2 through 11-6 for any operating system locale and still return the correct results. Rather than localize all AQS queries, the recommended best practice is to create your queries using English keywords and then specify the search term in the language of the installed Windows operating system.

Search Scope

If you do not specify a property keyword, the search defaults to all searched fields in a default scope. The default scope is per module, meaning that all contact folders, for example, use the same default scope. Keywords for fields that are searched within a default scope are marked with an asterisk (*) in Tables 11-2 through 11-6. For example, if you specify contactfirstname:lily, the search will return all items where the Contact first name starts with "lily." If you do not specify the contactfirstname keyword, the search will return all items where any of the searched fields for the Contacts module starts with "lily".

Keywords for All Modules

Table 11-2 lists some of the common keywords that can be used in any module with the exception of the Notes module, which only supports the subject, body, and categories keywords. For additional information about the <string>, <date>, <bool>, and <systemsize> specifiers shown in the table, see the relevant specifier topic later. For information about keywords and symbols that are valid for an AQS query, see the section "Keywords and Symbols" later in this chapter.

Table 11-2. Keywords for all Outlook modules

Search for

Keyword

Example

Attachment contains

attachment:<string>

attachment:office

Body*

contents:<string>

contents:office

Categories*

category:<string>

category:(oom AND isv)

Follow up*

followupflag:<string>

followupflag:completed

Has Attachments*

hasattachments:<bool>

hasattachments:true

Importance

importance:(low, normal, high)

importance:high

In Folder

folderpath:<string>

folderpath:(Sent Items)

Message size

messagesize:<systemsize>

messagesize:(> 500kb <700kb)

Modified

modified:<date>

modified:(last week)

Read

read:<bool>

read:yes

Received

received:<date>

received:(last month)

Sensitivity

sensitivity:(normal, personal, private,

confidential)

sensitivity:private

Sent

sent:<date>

sent:this week –monday

Subject*

subject:<string>

subject:office

NoteNote

The folderpath keyword should only be used when you search all items in a module. When you specify folderpath for a query, you should also specify the OlSearchScope.olSearchScopeAllFolders value for the SearchScope parameter of the Search method.

Keywords for Mail Module

Table 11-3 lists additional AQS keywords that can be used in the Mail module. This list is not exhaustive.

Table 11-3. Keywords for Outlook mail folders

Search for

Keyword

Example

Bcc*

bcc:<string>

bcc:(fadi fakhouri)

Cc*

cc:<string>

cc:(janet leverling)

Due Date

due:<date>

due:(11/1/06..12/1/06)

From*

from:<string>

from:Mark OR Dan

Received

received:<date>

received:(last month)

Sent

sent:<date>

sent:this week–today

Start Date

start:<date>

start:1/1/07

To*

to:<string>

to:(nancy davolio)

Keywords for Calendar Module

Table 11-4 lists additional keywords that can be used in the Calendar module. This list is not exhaustive.

Table 11-4. Keywords for Outlook calendar folders

Search for

Keyword

Example

End

end:<date>

end:(<2/1/07)

Location*

meetinglocation:<string>

meetinglocation:36

Optional Attendees*

optionalattendees:<string>

optionalattendees:(ryan gregg)

Organizer*

organizer:<string>

organizer:(nancy davolio)

Recurring

recurring:<bool>

recurring:true

Required Attendees*

requiredattendees:<string>

requiredattendees:(randy byrne)

Resources*

resources:<string>

resources:(conf room 36/2731)

Show Time As

freebusystatus:(free, busy, tentative, out of office)

freebusystatus:tentative

Start

start:<date>

start:tomorrow

Keywords for Contacts Module

Table 11-5 lists additional AQS keywords that can be used in the Contacts module. This list is not exhaustive.

Table 11-5. Keywords for Outlook contacts folders

Search for

Keyword

Example

Business Address*

businessaddress:<string>

businessaddress:microsoft

Business Phone*

businessphone:<string>

Businessphone:("425")

City

city:<string>

city:redmond

Company*

company:<string>

company:Microsoft

Department

department:<string>

department:payroll

E-mail*

emailaddress:<string>

emailaddress:microsoft.com

First Name

contactfirstname:<string>

contactfirstname:nancy

Full Name

fullname:<string>

fullname:(peter allenspach)

Home Address*

homeaddress:<string>

homeaddress:17560

Home Phone*

homephone:<string>

homephone:("425")

IM Address

imaddress:<string>

imaddress:lily

Job Title*

jobtitle:<string>

jobtitle:director

Last Name

contactlastname:<string>

contactlastname:"davolio"

Mailing Address

mailingaddress:<string>

mailingaddress:street

Mobile Phone*

mobilephone:<string>

mobilephone:("212")

Other Address*

otheraddress:<string>

otheraddress:way

PO Box

pobox:<string>

pobox:121

Primary Phone

primaryphone:<string>

primaryphone:("518")

State

stateorprovince:<string>

stateorprovince:ca

Street Address

streetaddress:<string>

street:("16255 NE 36th Way")

Title

personaltitle:<string>

personaltitle:ms

Web Page

webpage:<string>

webpage:msdn

Zip/Postal Code

postalcode:<string>

postalcode:98

Keywords for Tasks Module

Table 11-6 lists additional AQS keywords that can be used in the Tasks module. This list is not exhaustive.

Table 11-6. Keywords for Outlook task folders

Search for

Keyword

Example

Completed

iscompleted:<bool>

iscompleted:no

Date Completed

datecompleted:<date>

datecompleted:(last week)

Due Date

due:<date>

due:(last week)

Owner*

taskowner:<string>

taskowner:zoe

Priority

priority:(low, normal, high)

priority:high

Reminder Time

remindertime:<date>

remindertime:(next week)

Start Date

start:<date>

start:(next week)

Status

taskstatus:(in progress, completed, not started, waiting on someone else, deferred)

taskstatus:in progress

Keywords and Symbols

You can use various operators and symbols for queries that add power to the simplicity of AQS. Keywords and symbols for queries are listed in Table 11-7.

Table 11-7. Operators and symbols for specifiers

Operator/Symbol

Example

Results

subject:fast –track

Finds items where subject starts with fast, but where any searched field in the default scope does not start with track.

+

category:"book" +"important"

Finds items categorized with book and important.

>

sent:>11/1/06

Finds items sent after 11/1/06.

<

received:<11/1/06

Finds items received before 11/1/06.

..

sent:11/1/06..11/5/06

Finds items sent after 11/1/06 and before 11/5/06.

AND

category:(book AND dev)

Finds items where category starts with book and category starts with dev.

NOT

subject:(fast NOT track)

Finds items where subject starts with fast, but where subject does not start with track.

OR

subject:(fast OR track)

Finds items where subject starts with fast or where subject starts with track.

Parentheses

subject:(fast track)

Finds items where subject starts with fast and subject starts with track in any order.

Quotation marks

subject:"fast track"

Finds items with subject containing the exact phrase fast track.

String Specifier <string>

You can easily specify a variety of search conditions using a string specifier. Without additional operators such as those described in Table 11-7, a string search performs the equivalent of a DASL ci_startswith comparison on the specified string. For example, specifying subject:off for the query would find items that start with "off" in the subject. Possible results would include off, office, official, and so forth. If you specified subject:"off" (the equivalent of a DASL ci_phrasematch comparison) in the query, then only items that have the exact phrase "off" in the subject would be returned.

Date Specifier <date>

To specify a date, use the property name followed by any one of the following:

  • A date literal such as sent:1/1/2007.

  • A relative date specifier such as received:(last week) or sent:monday or due:june. Note that you can use operators with relative date specifiers such as received:(last week) NOT received:monday.

  • A date range specifier such as two consecutive periods (..) or using > and < symbols such as the following: sent:10/1/06..10/4/06 or sent:>10/1/06 <10/4/06.

Boolean Specifier <bool>

To specify a Boolean property, follow the property name with a valid Boolean value such as true, yes, false, or no. For example, both hasattachments:true and iscompleted:no are valid Boolean specifiers.

Systemsize Specifier <systemsize>

A Systemsize specifier lets you query a property such as the size of an item. Typically you specify the size as a range using > and < or double periods. Use the kb and mb abbreviations (k and m are also valid shortcuts) to specify kilobytes and megabytes, respectively. For example, size:10kb..50kb and size:>1m are both valid Systemsize specifiers.

Keywords for Custom Properties

If you need to query custom properties in an AQS query, enclose the name of the custom property in brackets as follows.

[Preferred Gift]:diamonds

The custom property must exist in the UserDefinedProperties collection for the Folder object where you want to execute the search. You must search for a custom property in the folder where the custom property exists. If you attempt to search for a custom property across all folders by specifying the OlSearchScope.olSearchScopeAllFolders value for the SearchScope parameter of the Search method, the custom property search will not return the correct results. Custom property searches must be scoped to the current folder in the Explorer by specifying OlSearchScope.olSearchScopeCurrentFolder.

DASL

DASL excels at retrieving property-based results for item-level searches in folders. Unfortunately, DASL is cumbersome to write, and it would be an understatement to mention that the property formats are opaque. That being said, DASL is the most versatile query language for Outlook, so it’s worth the deep dive.

Property Specifiers

DASL uses namespace schema names to represent properties in its query syntax. For a complete discussion of the namespace formats, see Chapter 17, "Using the PropertyAccessor Object." Certain properties such as binary properties are invalid for use in a DASL query. To learn more about invalid properties, see the section "Invalid Properties" later in this chapter. All properties in DASL queries must be enclosed in double quotation marks. You can add a simple routine like the addQuotes procedure to do the job for you.

/// <summary> 
/// Adds double quotation marks to schema name 
/// </summary> 
/// <param name="schemaName"></param> 
/// <returns>string</returns> private string addQuotes(
/// string schemaName) 
{ 
    return ("\"" + schemaName + "\""); 
}
NoteNote

Namespace property specifiers are always case-sensitive. For example, urn:schemas:contacts:givenName is a valid specifier but urn:schemas:contacts:givenname is invalid. The only exception to this rule is for the representation of the Hex value in a Messaging Application Programming Interface (MAPI) proptag specification. The following schema names for PR_TRANSPORT_MESSAGE_HEADERS are both valid:

http://schemas.microsoft.com/mapi/0x007d001e

http://schemas.microsoft.com/mapi/0x007D001E

String Comparisons

The string comparisons that DASL filters support include equivalence, prefix, phrase, and substring matching. Note that when you filter on the Subject property, prefixes such as RE: and FW: are ignored.

  • **Equivalence Matching   **DASL filters perform string equivalence comparison by using the equal (=) operator. The value of the string property must be equivalent to the comparison string, with the exception of prefixes RE: and FW: as mentioned earlier.

    As an example, the following DASL query creates a filter for company name equals "Microsoft."

    string filter = "@SQL=" 
        + "\"" + "urn:schemas-microsoft-com:office:office#Company" + "\"" 
        + " = 'Microsoft'";
    

    As another example, assume that the folder you are searching contains items with the following subjects:

    • Question

    • Questionable

    • Unquestionable

    • RE: Question

    • The big question

    The following = restriction:

    string filter = "@SQL=" 
        + "\"" + "urn:schemas:httpmail:subject" + "\"" 
        + " = 'question'";
    

    will return the following results:

    • Question

    • RE: Question

  • **Prefix, Phrase, and Substring Matching   **DASL supports the matching of prefixes, phrases, and substrings in a string property using content indexer keywords ci_startswith and ci_phrasematch, and the keyword like. If a store is indexed, searching with content indexer keywords is more performant than with like. If your search scenarios include substring matching (which content indexer keywords do not support), use the like keyword in a DASL query.

    A DASL query can contain ci_startswith or ci_phrasematch, and like, but all string comparisons will be carried out as substring matching.

    ci_startswith

    The syntax of ci_startswith is as follows:

    <PropertySchemaName> ci_startswith <ComparisonString>

    where PropertySchemaName is a valid name of a property referenced by namespace, and ComparisonString is the string used for comparison.

    The keyword ci_startswith performs a search to match prefixes. It uses tokens (characters, word, or words) in the comparison string to match against the first few characters of any word in the string value of the indexed property. If the comparison string contains multiple tokens, every token in the comparison string must have a prefix match in the indexed property. For example:

    • Restricting for "sea" would match "search."

    • Restricting for "sea" would not match "research."

    • Restricting for "sea" would match "Subject: the deep blue sea."

    • Restricting for "law order" would match "law and order" or "law & order."

    • Restricting for "law and order" would match "I like the show Law and Order."

    • Restricting for "law and order" would not match "above the law."

    • Restricting for "sea creatures" would match "Nova special on sea creatures."

    • Restricting for "sea creatures" would match "sealife creatures."

    • Restricting for "sea creatures" would not match "undersea creatures."

    Using the same example in equivalence matching, assume that the folder you are searching contains items with the following subjects:

    • Question

    • Questionable

    • Unquestionable

    • RE: Question

    • The big question

    The following ci_startswith restriction

    string filter = "@SQL=" 
        + "\"" + "urn:schemas:httpmail:subject" + "\"" 
        + " ci_startswith 'question'";
    

    will return the following results:

    • Question

    • Questionable

    • RE: Question

    • The big question

    ci_phrasematch

    The syntax of ci_phrasematch is as follows:

    <PropertySchemaName> ci_phrasematch <ComparisonString>

    where PropertySchemaName is a valid name of a property referenced by namespace, and ComparisonString is the string used for comparison.

    The keyword ci_phrasematch performs a search to match phrases. It uses tokens (characters, word, or words) in the comparison string to match entire words in the string value of the indexed property. Tokens are enclosed in double quotes or parentheses. Each token in the comparison string must have an equivalence match, not a substring or prefix match. If the comparison string contains multiple tokens, every token in the comparison string must have an equivalence match. Any word within a multiple word property like Subject or Body can match; it doesn’t have to be the first word. For example:

    • Restricting for "cat" would match "cat," "cat box," "black cat."

    • Restricting for "cat" would match "re: cat is out."

    • Restricting for "cat" would not match "catalog," "kittycat."

    • Restricting for "kitty cat" would match "put the kitty cat out."

    • Restricting for "kitty cat" would not match "great kitty catalog."

    Using the same example in equivalence matching, assume that the folder you are searching contains items with the following subjects:

    • Question

    • Questionable

    • Unquestionable

    • RE: Question

    • The big question

    The following ci_phrasematch restriction

    string filter = "@SQL=" 
        + "\"" + "urn:schemas:httpmail:subject" + "\"" 
        + " ci_startswith 'question'";
    

    will return the following results:

    • Question

    • RE: Question

    • The big question

    like

    The keyword like performs prefix, substring, or equivalence matching. Tokens (characters, word, or words) are enclosed with the % character in a specific way depending on the type of matching: like '<token>%' provides prefix matching. For example, restricting for like 'cat%' would match "cat" and "catalog." like '%<token>%' provides substring matching. For example, restricting for like '%cat%' would match "cat," "catalog," "kittycat," and "decathlon." like '<token>' provides equivalence matching. For example, restricting for like 'cat' would match "cat" and "RE: Cat."

    Each token can match any part of a word in the string property. If the comparison string contains multiple tokens, every token in the comparison string must have a substring match. Any word within a multiple word property like Subject or Body can match; it does not have to be the first word.

    Using the same example in equivalence matching, assume that the folder you are searching contains items with the following subjects:

    • Question

    • Questionable

    • Unquestionable

    • RE: Question

    • The big question

    The following like restriction

    string filter = "@SQL=" 
        + "\"" + "urn:schemas:httpmail:subject" + "\"" 
      + " like '%question%'";
    

    will return the following results:

    • Question

    • Questionable

    • Unquestionable

    • RE: Question

    • The big question

Searching the Body of an Item

To search for items that contain a specific word in the body, use the ci_startswith, ci_phrasematch, or like operator with the namespace representation of the body property, urn:schemas:httpmail:textdescription. When you use a DASL query to search the item body, you cannot determine where in the body the match was found. If you need to determine where the word was found in the body, examine the Body property of the found item. The following code sample creates a filter for items using the ci_phrasematch keyword, uses the filter for the Table object, and writes the Subject property to the trace listeners in the Listeners collection.

private void DemoSearchBody() 
{ 
    string filter; 
    if (Application.Session.DefaultStore.IsInstantSearchEnabled) 
    { 
        filter = "@SQL=" + "\"" 
            + "urn:schemas:httpmail:textdescription" + "\"" 
            + " ci_phrasematch 'office'"; 
    } 
    else 
    { 
        filter = "@SQL=" + "\"" 
            + "urn:schemas:httpmail:textdescription" + "\"" 
            + " like '%office%'"; 
    } 
    Outlook.Table table = Application.Session.GetDefaultFolder( 
        Outlook.OlDefaultFolders.olFolderInbox).GetTable( 
        filter, Outlook.OlTableContents.olUserItems); 
    while (!table.EndOfTable) 
    { 
        Outlook.Row row = table.GetNextRow(); 
        Debug.WriteLine(row["Subject"]); 
    } 
}  

Searching Attachments

To search for items in an indexed store that contain a specific word in an attachment, use the ci_startswith, ci_phrasematch, or like operator with the PR_SEARCH_ATTACHMENTS property. This property causes Outlook to evaluate the search criteria against the contents of item attachments. Item attachments are represented by the Attachments collection of the parent item. The indexer must be able to parse the contents of the attachment for the query to return reliable results. When you use a DASL query to search item attachments, you cannot determine where in the attachment the match was found or in which attachment the match was found (in the case of an item with multiple attachments). The following code sample creates a filter for items using the ci_phrasematch keyword, uses the filter for the Table object, and writes the Subject property to the trace listeners in the Listeners collection.

private void DemoSearchAttachments() 
{ 
    string filter; 
    const string PR_SEARCH_ATTACHMENTS =  
        "http://schemas.microsoft.com/mapi/proptag/0x0EA5001E"; 
    if (Application.Session.DefaultStore.IsInstantSearchEnabled) 
    { 
        filter = "@SQL=" + "\"" 
            + PR_SEARCH_ATTACHMENTS + "\"" 
            + " ci_phrasematch 'office'"; 
        Outlook.Table table = Application.Session.GetDefaultFolder( 
            Outlook.OlDefaultFolders.olFolderInbox).GetTable( 
            filter, Outlook.OlTableContents.olUserItems); 
        while (!table.EndOfTable) 
        { 
            Outlook.Row row = table.GetNextRow(); 
            Debug.WriteLine(row["Subject"]); 
        } 
    } 
}

Boolean Properties in DASL Syntax

In DASL syntax, you must convert True/False to an integer value, where 0 represents False and 1 represents True; likewise for Yes/No and On/Off. The DASL filter to return unread items is as follows.

string filter = "@SQL=" + "\"" + "urn:schemas:httpmail:read" +  
     "\"" + "=0";

Keywords Properties

The following discussion uses the Categories property as an example, but it can apply as well to any multivalued string property. The Categories property is of Outlook-type keywords, which is designed to hold multiple values. In MAPI, a Keywords property corresponds to the type PT_MV_STRING8. For additional information on MAPI property types, see Chapter 17.

To overcome the limitations of keyword restrictions using the Jet query syntax discussed later in this chapter, use DASL syntax that allows different restriction types such as phrase matching (ci_phrasematch keyword), starts with matching (ci_startswith keyword), or substring matching (like keyword). The following criteria string will find all items that contain "Business" as a category or as a word in a category, such as an item with the categories "Business" and "Business Intelligence." This filter string will succeed for all items that have "Business" as an exact word in the category field, even if the category has "Business" as one of the words, or the categories field contains more than one category.

string filter = "@SQL=" + "\"" 
    + "urn:schemas-microsoft-com:office:office#Keywords" 
    + "\"" + " ci_phrasematch 'Business'";

If the multivalued property is added to the Columns collection of the Table object using a reference by namespace, the format of the values of the property is an array of strings. To access these values, parse the elements in the array. Using the last example, this would also allow you to obtain the items that contain exactly "Business" as a category.

Custom Properties in a DASL Query

In a DASL query, if the name of a custom property contains spaces, you must apply Uniform Resource Locator (URL) encoding to each space character and replace the space with "%20". In general, URL encoding applies the same way to characters in a DASL query as in a URL.

Outlook custom properties can be added in the Outlook user interface through the Field Chooser or added programmatically with the Add method of the UserProperties collection on an item or the UserDefinedProperties collection on a Folder object. When you construct a DASL query for an Outlook custom property, you must use the namespace globally unique identifier (GUID) for Outlook custom properties in the following format:

http://schemas.microsoft.com/mapi/string/{GUID}/PropertyName

where {GUID} is the following GUID:

{00020329-0000-0000-C000-000000000046}

The following DASL query uses the content indexer keyword ci_phrasematch and retrieves all contacts where the custom property named "Preferred Gift" matches "Diamonds".

string filter = "@SQL=" + "\"" 
    + "http://schemas.microsoft.com/mapi/string/"
    + "{00020329-0000-0000-C000-000000000046}/Preferred%20Gift"
    + "\"" + "ci_phrasematch 'Diamonds'";

If the custom property you are searching for does not exist in the UserDefinedProperties collection for a folder, you must append a type specifier to the namespace representation of the custom property. This requirement only applies to a DASL filter for the Items collection, the Table object, or the AdvancedSearch method. Only properties that use the String namespace require appending the type specifier. For additional information about MAPI type specifiers, see "Type Specifiers" in Chapter 17. For example, assume that you want to search for the Unicode string property named MyProperty, and this property does not exist in the UserDefinedProperties collection for a folder. The following DASL query appends the Unicode string type specifier (/0x0000001f) to the String namespace representation of the property.

string filter = "@SQL=" + "\"" 
    + "http://schemas.microsoft.com/mapi/string/"
    + "{00020329-0000-0000-C000-000000000046}/MyProperty"
    + "/0x0000001f" + "\"" + " = '12-74440'";

Building DASL Queries

Fortunately, there is a quick way to build DASL queries without too much pain. You can use an undocumented Registry key to display a Query Builder tab on the Filter dialog box associated with the View Summary dialog box (see Figure 11-2). After you use the Query Builder to construct your query, you can then copy the Filter syntax displayed on the SQL page and paste it into your code. Do not attempt to add the Query Builder page Registry setting unless you are familiar with the Windows Registry Editor.

Figure 11-2. The Query Builder tab of the Filter dialog box

Query Builder tab of the Filter dialog box

To display the Query Builder tab of the Filter dialog box, follow these steps:

  1. Click Start and point to Run. In the Run dialog box, type Regedit, and then click OK to launch the Windows Registry Editor.

  2. In the Registry tree, navigate to HKEY_CURRENT_USER\Software \Microsoft\Office\12.0\Outlook.

  3. From the Edit menu, select New, and then select Key.

  4. In the Key edit box, type QueryBuilder. Regedit will suggest New Key #1, but you should replace that key name with QueryBuilder.

To build a filter using the Query Builder tab of the Filter dialog box, follow these steps:

  1. In Outlook, from the View menu, select Current View, and then select Customize Current View.

  2. In the View Summary dialog box, click Filter.

  3. In the Filter dialog box, click the Query Builder tab.

  4. Use the Query Builder interface to build your query. When you construct a filter, you actually build a WHERE clause without the WHERE keyword. Notice that you can use the logical AND or logical OR operator to develop the query and move clauses up or down.

  5. In the Filter dialog box, click the SQL tab shown in Figure 11-3 and clear the Edit These Criteria Directly check box. Once you clear the check box, you can copy the query by selecting it and pressing CTRL+C to copy it to the Clipboard.

    Figure 11-3. Copy a Filter string from the SQL tab of the Filter dialog box to construct a DASL query

    Copy a Filter string to construct a DASL query

  6. Because you don’t want to modify the view, click Cancel to close the Filter dialog box. Click Cancel again to close the View Summary dialog box.

Once you have constructed your filter string, the rest of the process is relatively straightforward. Paste the DASL string into the Microsoft Visual Studio code editor. If you are creating a filter for Table.Restrict or Items.Restrict, you need to concatenate the @SQL= prefix for your query to operate correctly. For a filter that is passed to the AdvanceSearch method of the Application object or used with View.Filter, you do not add the @SQL= prefix. Using the Query Builder page is certainly easier than typing long DASL strings manually.

Jet

The Jet query syntax is the easiest to learn and use in your code, but it does not have the power of DASL. Jet queries can create restrictions for most built-in and custom properties. When you create a Jet query, be aware that there are certain computed and binary properties that are invalid and will cause Outlook to raise an error. Jet query syntax also does not support the new content indexer keywords that leverage the Instant Search feature in Outlook 2007. Consequently, Jet queries will return results slower than DASL queries provided that Instant Search is installed and enabled.

Property Specifiers

Jet property specifiers use the English name of the property enclosed in square brackets to represent built-in properties in Jet queries. The English name of the property is identical with the object model name of the property. Based on this convention, you can use [Subject] in your Jet query independent of locale to create a restriction on the Subject property of an item. Custom properties use the locale-specific name of the property enclosed in square brackets.

Tip   Don’t confuse the object model name of the property with the Field Chooser name of the property, which is localized. For example, if French is the user interface (UI) language, then the Field Chooser will display a field named Sujet that represents the Subject property of a MailItem object. In a Jet equivalence query for "Office 2007" using the Subject property, the filter would be [Subject] = 'Office 2007' whether the UI language is French or English. Be aware that object model names for built-in properties have no spaces or special characters, whereas the Field Chooser name can contain both spaces and special characters. For example, the object model name for an assistant’s phone number is AssistantTelephoneNumber. In the Field Chooser, this property is Assistant’s Phone. Always use the object model name in your Jet queries.

String Comparisons

The string comparison that Jet filters support is limited to equivalence matching as long as the property is not a keywords property. String comparisons for keywords properties use phrase matching. See the section "Keywords Properties in a Jet Query" later in this chapter. You can filter items based on the value of a string property being equivalent to a specific string, for example, the LastName property being equal to "Davolio." Note that the comparison is not case-sensitive; in the last example, specifying "Davolio" and "davolio" as the comparison string will return the same results.

When matching string properties, you can use either an apostrophe (') or double quotation marks ("") to delimit the values that are part of the filter. For example, all of the following lines function correctly when the property is a string.

string filter = "[CompanyName] = 'Microsoft'"; 
string filter = "[CompanyName] = " + "\"" + "Microsoft" + "\"";
NoteNote

If the search string contains a single quote character, escape the single quote character in the string with another single quote character. Similarly, if the search string contains a double quote character, escape the double quote character in the string with another double quote character.

Boolean Properties in Jet Syntax

In Jet syntax, Boolean operators such as True/False, Yes/No, On/Off, and so on should be used as is and should not be converted to a string. For example, to create a filter to return unread items, create a filter as follows.

string filter = "[UnRead] = True";
NoteNote

If you convert the Boolean value to a comparison string by enclosing it in quotation marks, then a Jet filter using any nonempty comparison string and filtering on a Boolean property will return items that have the property True. A Jet filter comparing an empty string with a Boolean property will return items that have the property False.

Custom Properties in a Jet Query

Custom properties are defined as properties that exist in the UserDefinedProperties collection for the folder that contains the item, and in the UserProperties collection for the item in the folder. Custom properties can contain spaces in the property name. In a Jet query, as in all property name references, simply enclose the custom property name in square brackets. For example, the following Jet query retrieves all contacts where the custom property named Preferred Gift is exactly "Diamonds." For the query to succeed, the custom property named Preferred Gift must exist in the UserDefinedProperties collection in the folder that contains the custom contact items.

string filter = "[Preferred Gift] = 'Diamonds'";

Keywords Properties in a Jet Query

Keywords properties are defined as Outlook properties that can contain multiple values. Typically keywords properties are multivalued string properties such as the Categories property on an item. In a Jet query, you only perform phrase matching on a keywords property. You cannot perform starts with or substring matching with a Jet query. Use a DASL query for starts with or substring restrictions. Consider the following criteria for Table.Restrict.

string filter  = "[Categories] = 'Partner'"; 

This Jet query will return rows for items where the Categories property for the item finds a phrase match for "Partner." It will return rows for items that are categorized as "Partner." It will also return rows for items that are categorized as "Partner" and "Important" and for items that are categorized as "Tier1 Partner." It will not return rows for items where the item category is only "Partnership."

Date-Time Comparisons

The following section covers date-time comparisons and filters for both Jet and DASL queries. The important concept for these restrictions is that Jet filters are evaluated against local time, while DASL filters are evaluated against Coordinated Universal Time (UTC). If you don’t understand this distinction, you could return the wrong results from your date-time query because the query is formulated incorrectly. For example, if you want to issue a DASL query against a local date-time value, you must first convert the local time value to its equivalent UTC date-time value for the query to operate correctly.

Filtering Recurring Items in the Calendar Folder

To filter a collection of appointment items that include recurring appointments, you must use the Items collection. The Table object only returns rows that represent the master series appointment and does not support recurring items in a calendar folder. Use the Items.IncludeRecurrences property to specify that Items.Find or Items.Restrict should include recurring appointments. If IncludeRecurrences is True, you can filter recurring appointment items only on the Start and End properties. Use a Jet query to specify the Start and End properties.

If you need to obtain a subset of filtered recurring appointments, use the Find and FindNext methods or create a new restriction on an Items collection that contains recurrences. For example, the following SearchRecurringAppointments procedure creates an Items collection that contains recurring appointments between 8/9/2006 and 12/14/2006. It then uses the Find and FindNext methods to find recurring appointments that contain "Office" in the subject. An alternative, preferred method is to create a new Items collection by restricting the original Items collection. The filter for the new Items collection uses the ci_startswith keyword to create a subset of recurring items that start with "Office" in the subject.

private void SearchRecurringAppointments() 
{ 
    Outlook.AppointmentItem appt = null; 
    Outlook.Folder folder = 
        Application.Session.GetDefaultFolder( 
        Outlook.OlDefaultFolders.olFolderCalendar) 
        as Outlook.Folder; 

    // Set start value.
    DateTime start =  
        new DateTime(2006, 8, 9, 0, 0, 0); 

    // Set end value.
    DateTime end =  
        new DateTime(2006, 12, 14, 0, 0, 0); 

    // Initial restriction is Jet query for date range.
    string filter1 = "[Start] >= '" + 
        start.ToString("g") 
        + "' AND [End] <= '" + 
        end.ToString("g") + "'"; 
    Outlook.Items calendarItems = folder.Items.Restrict(filter1); 
    calendarItems.Sort("[Start]", Type.Missing); 
    calendarItems.IncludeRecurrences = true; 

    // Must use 'like' comparison for Find/FindNext.
    string filter2; 
        filter2 = "@SQL=" 
            + "\"" + "urn:schemas:httpmail:subject" + "\"" 
            + " like '%Office%'"; 

    // Create DASL query for additional Restrict method.
    string filter3; 
    if (Application.Session.DefaultStore.IsInstantSearchEnabled) 
    { 
        filter3 = "@SQL=" 
            + "\"" + "urn:schemas:httpmail:subject" + "\"" 
            + " ci_startswith 'Office'"; 
    } 
    else 
    { 
        filter3 = "@SQL=" 
            + "\"" + "urn:schemas:httpmail:subject" + "\"" 
            + " like '%Office%'"; 
    } 

    // Use Find and FindNext methods.
    appt = calendarItems.Find(filter2) 
        as Outlook.AppointmentItem; 
    while (appt != null) 
    { 
        StringBuilder sb = new StringBuilder(); 
        sb.AppendLine(appt.Subject); 
        sb.AppendLine("Start: " + appt.Start); 
        sb.AppendLine("End: " + appt.End); 
        Debug.WriteLine(sb.ToString()); 

        // Find the next appointment.
        appt = calendarItems.FindNext() 
            as Outlook.AppointmentItem; 
    } 

    // Restrict calendarItems with DASL query.
    Outlook.Items restrictedItems = 
        calendarItems.Restrict(filter3); 
    foreach (Outlook.AppointmentItem apptItem in restrictedItems) 
    { 
        StringBuilder sb = new StringBuilder(); 
        sb.AppendLine(apptItem.Subject); 
        sb.AppendLine("Start: " + apptItem.Start); 
        sb.AppendLine("End: " + apptItem.End); 
        sb.AppendLine(); 
        Debug.WriteLine(sb.ToString()); 
    } 
 
}

Date-Time Format of Comparison Strings

Date-time values are recognized according to the time format, short date format, and long date format settings in Regional and Language Options in the Windows Control Panel.

Although dates and times are typically stored with a date format, filters using the Jet and DASL syntax require that the date-time value be converted to a string representation. In Jet syntax, the date-time comparison string should be enclosed in either double quotes or single quotes. In DASL syntax, the date-time comparison string should be enclosed in single quotes.

To make sure that the date-time comparison string is formatted as Outlook expects, use the constructor of the DateTime structure and the ToString method with the "g" format specifier, which converts the DateTime value to a string that will be interpreted correctly by Outlook. The following example creates a Jet filter to find all contacts modified before June 12, 2005, at 3:30 p.m. local time.

string filter = "[LastModificationTime] < '" + 
    new DateTime(2005, 6, 12, 15, 30, 0).ToString("g") + "'";

Date-Time Literals for Outlook Date-Time Comparisons

Outlook evaluates date-time strings based on the date and time settings in the Regional and Language Options in Windows Control Panel. Specifically, Outlook evaluates dates according to the short date format and time according to the time format without seconds. If you specify seconds in the date-time string, the query will fail to operate as expected.

The format used by Outlook corresponds to the General (short date and short time) pattern in the DateTimeFormatInfo class. If you use the Parse method of the DateTime structure, you should be certain that the argument to the Parse method follows the short date and short time format for the current locale. If you use the constructor for the DateTime structure, you need to specify year, month, day, hour, minute, and second arguments and then use the ToString method with the "g" format specifier to convert the date-time value to the short date and short time string expected by Outlook. The date specifier argument to the ToString method is case-sensitive, so be sure to use "g" as the format specifier. When you use the DateTime constructor and the "g" format specifier in the ToString method, you create a date-time literal that will be interpreted correctly by Outlook.

Time Zones Used in Comparison

When an explicit built-in property is referenced in a Jet query with its explicit string name, the comparison evaluates the property value and the date-time comparison string as local time values.

When a property is referenced in a DASL query by namespace, the comparison evaluates the property value and the date-time comparison string as UTC values. For example, the following DASL query finds all contacts modified before June 12, 2005, at 3:30 p.m. UTC.

string filter = "@SQL=" + "\"" + "DAV:getlastmodified" + "\"" +  
    " < '" + new DateTime(2005, 6, 12, 15, 30, 0).ToString("g") + "'";

Conversion to UTC for DASL Queries

Because DASL queries always perform date-time comparisons in UTC, if you use a date literal in a comparison string, you must use its UTC value for the comparison. You can use the Row.LocalTimeToUTC helper function or Outlook date-time macros to facilitate the conversion.

NoteNote

You should use the local time to UTC conversion functions built into the Outlook object model to perform conversions from local date-time values to UTC date-time values rather than DateTime structure conversion functions such as ToLocalTime and ToUniversalTime.

LocalTimeToUTC

One way to facilitate local time to UTC conversion is to use the helper function, LocalTimeToUTC, of the Row object. The following line of code uses this helper function to convert the value of the LastModificationTime property (which is a default column in all Table objects).

DateTime modified = nextRow.LocalTimeToUTC("LastModificationTime");

Outlook Date-Time Macros

The date macros listed in Table 11-8 return filter strings that compare the value of a given date-time property with a specified relative date or date range in UTC; SchemaName is any valid date-time property referenced by namespace.

NoteNote

Outlook date-time macros can be used only in DASL queries.

For example, the DemoDASLDateMacro procedure creates a DASL query that filters for items that were modified in the last month, creates a Table object with that filter, and then enumerates rows in the restricted Table object.

private void DemoDASLDateMacro() 
{ 
    string filter = "@SQL=" + "%lastmonth(" + "\"" + 
        "DAV:getlastmodified" + "\"" + ")%"; 
    Outlook.Table table = Application.Session.GetDefaultFolder( 
        Outlook.OlDefaultFolders.olFolderInbox).GetTable( 
        filter, Outlook.OlTableContents.olUserItems); 
    while(!table.EndOfTable) 
    { 
        Outlook.Row row = table.GetNextRow(); 
        Debug.WriteLine(row["Subject"]); 
    } 
}

Table 11-8. Outlook date macros for DASL queries

Macro

Syntax

Description

today

%today("SchemaName")%

Restricts for items with SchemaName property value equal to today.

tomorrow

%tomorrow("SchemaName")%

Restricts for items with SchemaName property value equal to tomorrow.

yesterday

%yesterday("SchemaName")%

Restricts for items with SchemaName property value equal to yesterday.

next7days

%next7days("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to next 7 days.

last7days

%last7days("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to last 7 days.

nextweek

%nextweek("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to next week.

thisweek

%thisweek("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to this week.

lastweek

%lastweek("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to last week.

nextmonth

%nextmonth("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to next month.

thismonth

%thismonth("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to this month.

lastmonth

%lastmonth("SchemaName")%

Restricts for items with SchemaName property

values in range equivalent to last month.

Integer Comparisons

You can compare an integer property with an integer value in a filter string using Jet or DASL syntax. You can specify the integer value with or without quotation marks as delimiters. The following Jet filter can be used to restrict on the condition that the Importance value is high.

string filter = "[Importance] = 2";

If you want to use a value from an integer enumeration, convert the value to a string and append it to the filter string. The following filters are equivalent and test for items with Importance set to high.

string filter = "[Importance] = " + 
    Outlook.OlImportance.olImportanceHigh.ToString(); 
 
string filter = "@SQL=" + "\"" + "urn:schemas:httpmail:importance"  
    + "\"" + " = 2";

Invalid Properties

Not every property can be used in a filter string for Items.Restrict or Table.Restrict. For both Jet and DASL queries, you cannot restrict on a binary property such as EntryID. You also cannot restrict for computed properties such as BodyFormat or RecurrenceState. There are some exceptions for DASL properties that are noted later.

Jet

The properties listed in Table 11-9 are invalid in a Jet restriction. Outlook will raise a "Condition is not valid" error if you attempt to use one of the properties listed here in a restriction string.

Table 11-9. Invalid properties for a Jet restriction

AutoResolvedWinner

Body

BodyFormat

Class

Companies

CompanyLastFirstNoSpace

CompanyLastFirstSpaceOnly

ContactNames

Contents

ConversationIndex

DLName

DownloadState

Email1EntryID

Email2EntryID

Email3EntryID

EntryID

HtmlBody

InternetCodePage

IsConflict

IsMarkedAsTask

LastFirstAndSuffix

LastFirstNoSpace

LastFirstNoSpaceAndSuffix

LastFirstNoSpaceCompany

LastFirstSpaceOnly

LastFirstSpaceOnlyCompany

MeetingWorkspaceURL

MemberCount

NetMeetingAlias

NetMeetingServer

Permission

PermissionService

ReceivedByEntryID

ReceivedOnBehalfOfEntryID

RecurrenceState

ReplyRecipients

ResponseState

Saved

Sent

Submitted

TaskSubject

VotingOptions

DASL

If a schema name representation of the Jet property exists, using the schema name property in a DASL restriction will also cause Outlook to raise an error. However, there are some exceptions to this rule. For example, you can use the schema name for plain text body to create a restriction as follows.

string filter = "@SQL=" + 
    "\"" + "urn:schemas:httpmail:textdescription" + "\"" + 
    " ci_startswith 'Office'";

Another notable exception is the IsMarkedAsTask property. You can use the schema name representation of this property to create a filter for items that are marked as a task.

string filter = "@SQL=" + "\"" + 
    http://schemas.microsoft.com/mapi/proptag/0x0E2B0003 
    + "\"" + " = 1";

Comparison and Logical Operators

You can write queries that range from the simple Subject equivalence query in Jet to complex DASL queries. The following sections list valid comparison and logical operators for both Jet and DASL queries.

Comparison Operators

Table 11-10 lists valid comparison operators in filter strings using Jet or DASL syntax.

Table 11-10. Comparison operators

Operator

Description

<

Performs a less-than comparison.

>

Performs a greater-than comparison.

<=

Performs a less-than-or-equal-to comparison.

>=

Performs a greater-than-or-equal-to comparison.

<>

Performs a not-equal-to comparison.

=

Performs an equal-to comparison.

Logical Operators

You can use the logical operators And, Not, and Or in filter strings in Jet or DASL syntax. The order of precedence of these operators, from the highest to the lowest, is Not, And, Or. You can use parentheses to indicate specific precedence in a filter. Logical operators are case-insensitive.

Not

Not performs a logical NOT on the condition. The following Jet query retrieves all contacts with a first name of Jane who do not work at Microsoft.

string filter = "[FirstName] = 'Jane'" + 
    " And Not([CompanyName] = 'Microsoft')";

And

And performs a logical AND on the condition. The following Jet query retrieves all contacts who work at Microsoft and have a first name of Marina.

string filter = "[FirstName] = 'Marina'" + 
    " And [CompanyName] = 'Microsoft'";

Or

Or performs a logical OR on the condition. The following code returns all contact items that have a first name of either Peter or Paul.

string filter = "[FirstName] = 'Peter' Or [FirstName] = 'Paul'";

Null Comparisons

To perform null comparisons, use the Is Null keywords in a DASL query. Is Null is invalid in a Jet query. Is Null returns True if the property is null and False if the property is not null.

Is Null operations are useful to determine if a date property has been set or if a string property is empty. If the date is null, the local time value of the date will be equal to 1/1/4501.

The syntax of Is Null is as follows

[PropertyName] IS NULL

where PropertyName is the name of a property referenced by namespace.

You can combine the Is Null keywords with the Not operator to evaluate if a property is not null. The following DASL query retrieves all contacts where the custom property Order Date is not null and the CompanyName property is exactly Microsoft.

string filter = "(NOT(" + 
    AddQuotes("http://schemas.microsoft.com/mapi/string/" 
    + "{00020329-0000-0000-C000-000000000046}/Order%20Date") 
    + " IS NULL) AND " 
    + AddQuotes("urn:schemas-microsoft-com:office:office#Company") 
    + " = 'Microsoft')"; 

Search Entry Points

The number of object model entry points for search has expanded significantly in Outlook 2007. Table 11-11 lists the entry points for search in the Outlook object model. This table provides a good overview of the search and filtering features of the Outlook 2007 object model. Some of the features, such as the Search method of the Explorer object or the Filter property of the View object, only return results in the Outlook UI. Other features, such as the Restrict method on the Items collection or the Table object, return results programmatically. Choose the most appropriate entry point for your specific scenario.

Table 11-11. Search entry points

Entry point

Action

Comments

Application.AdvancedSearch

Returns a Search object using the criteria specified by the Filter parameter.

Use the AdvancedSearchComplete event on the Application object to determine when a given search has completed.

Filter must be a DASL query without the @SQL= prefix.

AutoFormatRule.Filter

Applies a filter to an AutoFormatRule object.

The Filter property is not persisted when you save the View object. You must re-create the filter dynamically. See the code sample for AutoFormatRule in Chapter 10, "Organizing Outlook Data." Filter must be a DASL query without the @SQL= prefix.

Explorer.Search

Based on the Query parameter passed to the method, performs a programmatic content indexer search that is analogous to a user executing a search from the Outlook UI.

Use the IsInstantSearchEnabled property of the Store object to determine if Instant Search is installed and enabled for a given Store object. The scope of the search is determined by the SearchAllItems parameter. If SearchAllItems is True, the method will search across all folders that have the same folder type as the current folder and all stores that have been selected for search in the Search Options dialog box. If SearchAllItems is False, the method will search only the folder represented by Explorer.CurrentFolder.Query can be any valid AQS query. You cannot use Jet or DASL syntax for the Query parameter.

Folder.GetTable

Returns a Table object containing rows determined by the Filter parameter.

Filter can be a Jet query or a DASL query with the @SQL= prefix.

Items.Find

Searches for the first item in the Items collection that satisfies the specified Filter parameter and returns an Object object representing the item.

Use the FindNext method to find the next item that meets the criteria established for the Find method. You should cast the returned item to the appropriate type. Filter can be a Jet query or a DASL query with the @SQL= prefix. If you use ci_phrasematch or ci_startswith in the filter for the Find method, Outlook will raise an error.

Items.Restrict

Filters a given set of items based on the Filter parameter and returns another Items collection.

Filter can be a Jet query or a DASL query with the @SQL= prefix.

Search.GetTable

Returns a Table object containing rows determined by the Filter parameter passed to Application. AdvancedSearch.

You cannot filter the Table object returned by Search.GetTable by calling Table.Restrict.

Table.FindRow

Searches for the first row in the Table object that satisfies the specified filter and returns a Row object representing the item.

Use the FindNextRow method to find the next row that meets the criteria established for the FindRow method. Filter can be a Jet query or a DASL query with the @SQL= prefix. If you use ci_phrasematch or ci_startswith in the filter for the FindRow method, Outlook will raise an error.

Table.Restrict

Filters rows in the given table based on a specified filter and returns another Table object.

Filter can be a Jet query or a DASL query with the @SQL= prefix.

View.Filter

Sets a view’s filter without changing the view’s XML value. Setting the Filter parameter for a View object only changes the view in the user interface and does not result in a filtered Items collection.

Filter must be a DASL query without the @SQL= prefix.

View.XML

The XML property gets or sets the XML for a view. Modifying the <Filter> node changes the view’s filter. Setting the XML for a view only changes the view in the UI and does not result in a filtered Items collection.

View.XML is no longer recommended. Use the Filter property of the View object instead of modifying the <Filter> node in View.XML

Search Considerations

So, you know your solution requires search, but you don’t know which type of search will work best for your particular scenario. The following discussion provides some guidelines for programmatic search in Outlook. Not every scenario is covered here, but you should be able to save some time by reading this section with your specific scenario in mind.

Performance

The following guidelines are designed to help you decide how to code your search to achieve the best possible performance. In part, the performance decision you make depends on whether your search operates against Microsoft Exchange Server using online mode, Exchange Server using cached mode, or a Personal Folders File (.pst) without Exchange. Making your search operations perform as fast as possible is important for customer acceptance of your solution and for the performance of Outlook in general. Consider all of the following guidelines when you implement search for your solution.

If you need a persistent and long-lived aggregation of contents from multiple folders in a single store, consider using the AdvancedSearch method. The AdvancedSearch method requires the store to support search folders, which can degrade store performance, especially as the number of search folders grows. If the AdvancedSearch method is called against an Exchange Server store, there are additional performance considerations. If you issue repeated restrictions using the AdvancedSearch method of the Application object and restrict on different properties in the restriction, the performance of Exchange Server could be affected. When you call the AdvancedSearch method (regardless of whether you call Search.Save), a hidden search folder is created on Exchange Server. If you find that Exchange Server performance suffers due to the number of restrictions you issue against one or more folders, you should consider using the Restrict method for the Table object instead of the AdvancedSearch method. For additional details on performance degradation on Exchange Server, see the Microsoft Knowledge Base article at http://support.microsoft.com/kb/216076.

If you only need contents from a single folder (excluding a folder that contains appointment items), then the Restrict method or the FindRow method on the Table object should be the default choice. The AdvancedSearch method introduces unnecessary overhead. The Restrict method typically incurs one remote procedure call (RPC) each time the Restrict method is called. Another approach is to use the Find and FindNext methods on the Items collection or the FindRow and FindNextRow methods on the Table object. The find methods are appropriate when you have sorted items or rows and then want to seek rows within the Table object.

In general, you should prefer the Table object over the Items collection except for the case when you need to obtain recurring appointments. For cached Exchange mode and Post Office Protocol 3 (POP3) or Internet Message Access Protocol (IMAP) accounts using a Personal Folders File (.pst), using the Table object is almost an order of magnitude faster than using the Items collection without calling the SetColumns method. The improved performance of the Table object is especially noticeable when you enumerate or filter folders that contain a large number of items (more than 1,000). If your scenario requires that you use the Items collection, use the SetColumns method to improve performance. For details on the use of the Items collection and the Table object, see Chapter 6, "Accessing Outlook Data."

If you need recurring appointments for a Calendar folder, use the Items collection and set the IncludeRecurrences property to True. For a Calendar folder, the Table object only returns rows that represent the appointment series for recurring appointments. If you need to apply an additional restriction to the returned Items collection in this case, use the Restrict method of the Items collection to return a subset of recurring appointments. For complete details, see the section "Use IncludeRecurrences to Expand Recurring Appointments" in Chapter 6.

If the ExchangeConnectionMode property indicates that the user is operating in online Exchange mode, the Restrict method should generally be considered a relatively expensive operation for the store. Search folders with restrictions can basically be considered a special case of the Restrict method, although the longer life of a search folder does cut the performance cost to some degree. In many cases, the Restrict method can be changed to the FindRow and FindNextRow methods, possibly combined with client-side restriction evaluation. The most common case is when you have a restriction that narrows the range of a sortable column (for example, find all mail received in the past seven days). By sorting on the "key" of the restriction, you can use the FindRow method to navigate to the first matching row and then query rows until finding a non-matching row. This approach is less cost-effective, though, if the restricted set of rows are in multiple noncontiguous regions such that each call to the FindNextRow method could potentially result in its own RPC, whereas the Restrict method ensures that the matching rows are grouped together.

If the store is indexed (the IsInstantSearchEnabled property on the Store object returns True) and the property you are searching is a string property and exists in the index, then use ci_startswith or ci_phrasematch in a DASL query for the Restrict method on the Table object.

If you only want to display the search results in an Explorer window, choose an AQS query and use the Search method on the Explorer object. You cannot programmatically obtain the results from an AQS search.

Read-Only vs. Read/Write

If you need only to read information from a search, the Table object is the preferred method of obtaining search results subject to the performance considerations discussed earlier. If you need to span multiple folders, call the AdvancedSearch method and use the GetTable method on the Search object.

If you need to perform write operations on the results of a search, you have a couple of options:

  • Use the Table object for the search, ensure that the EntryID value is present in the Columns collection, and use the GetItemFromID method of the Namespace object to obtain a full item. You can then perform read/write operations on the full item. Because the table’s Columns collection can also return MessageClass, you can cast the item to the appropriate type based on MessageClass.

  • Use the Items collection for the search and enumerate the items using a foreach construct. You can perform read/write operations on the item. Remember that the Items collection can contain items of different types. Use the OutlookItem helper class discussed in Chapter 6 for read/write operations on common properties, or perform a cast to return the correct type. Once you have an item, make appropriate changes and then save the item.

Searching Subfolders

If you need to search in multiple folders, you should consider the GetTable method of the Search object. The AdvancedSearch method of the Application object returns a Search object. The scope for the search can span multiple folders and their subfolders in a given store. The scope for AdvancedSearch cannot span multiple stores. Coding Search.GetTable is a little more complicated than writing basic Table object code, simply because you must hook up event handlers for the AdvancedSearchComplete event on the Application object. See Chapter 10 for more information about the Search object.

Another factor that affects performance is whether Windows Desktop Search is installed and enabled for a given Store object. For Windows Vista, Windows Desktop Search is an integral component of the operating system. For Windows XP, Windows Desktop Search must be downloaded and installed as a separate component. Outlook will prompt the user to install Windows Desktop Search if it is not installed. Use the IsInstantSearchEnabled property of the Store object to determine if Instant Search is installed and enabled.

Important noteImportant

There is no way to determine programmatically that indexing is complete for a given store. In a first-run scenario when it can take several hours for indexing to complete, this can present a problem for content indexer searches. IsInstantSearchEnabled will return True even when indexing has not completed for a given folder or store.

Your code should always check the IsInstantSearchEnabled property of the Store object. This property will return False if Windows Desktop Search is not installed on Windows XP. It will also return False if the user or group policy has disabled Instant Search on a given Store. If IsInstantSearchEnabled returns False, you typically would create a like DASL restriction for string properties. If IsInstantSearchEnabled returns True, you can create a ci_startswith or ci_phrasematch DASL restriction for string properties. If you use a content indexer restriction and IsInstantSearchEnabled is False, Outlook will raise an error.

Summary

This chapter covered all the programmatic query languages for Outlook 2007: Jet, DASL, and AQS. The use of a particular query language depends on your scenario. For most operations, DASL queries are performant and additionally can use the Instant Search feature of Outlook 2007. Programmatic search and filtering are greatly improved in this version of Outlook, and you will be able to create some innovative solutions using these new search entry points in the Outlook object model.

Additional Resources