What's New for Developers in Outlook 2007 (Part 2 of 2)
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.
Summary: Part 2 of this article continues discussing the enhancements and additions for developers in Outlook 2007. It is accompanied by the What's New Add-In, which is available as a download. If you want to get a head start on the Outlook platform, the article and the add-in provide sufficient detail for you to begin coding against Outlook 2007. (32 printed pages)
Randy Byrne, Microsoft Corporation
May 2006
Applies to: Microsoft Office Outlook 2007
Download: Outlook 2007 Sample: What's New Add-Ins
Contents
Table Object
PropertyAccessor Object
AddressEntry Enhancements
Rules Objects
Sharing Objects
Context Menus
Conclusion
Additional Resources
Table Object
One of the most frequent complaints from developers has been about poor performance when using the Microsoft Office Outlook object model. In Microsoft Office Outlook 2007, the Table and related objects address these complaints. The Table object lets you enumerate items in a folder. It also provides you with the ability to specify properties as table columns and to filter and sort items as rows in the table. Unlike ItemProperties of an Outlook item object, which include all the explicit built-in properties and custom properties for that item, each row in the Table, by default, represents a partial set of properties of that item. For this reason, the new Table object offers a significant performance improvement over the legacy Items collection. For very large collections (more than 1,000 items), the Table performs in approximately 10 percent to 20 percent of the time required for enumerating an Items collection object without the use of Items.SetColumns. Unlike the Items collection, it encourages developers to write efficient code and does not have the disadvantages of the Items collection, where a developer could retrieve the Body property or the Attachments collection on the item and in turn see performance degrade dramatically.
Default Column Set
Based on data access models that are familiar to most developers, Table supports a Columns collection. The default Columns collection depends on the type of items (for example, appointment items, contact items, and mail items) contained in the parent Folder object of the Table. The default columns for different folder types are shown in Table 1. Note that you can remove columns and add columns as shown in the code sample for Table.
Table 1 shows the default columns for a Table generated for all Outlook folders, including the Inbox, Sent Items, and Deleted Items.
Table 1. Default columns for tables in all Outlook folders
Column |
Property |
---|---|
1 |
EntryID |
2 |
Subject |
3 |
CreationTime |
4 |
LastModificationTime |
5 |
MessageClass |
Table 2 shows the default columns for a Table generated for the Calendar folder.
Table 2. Default columns for tables in Calendar folder
Column |
Property |
---|---|
1 |
EntryID |
2 |
Subject |
3 |
CreationTime |
4 |
LastModificationTime |
5 |
MessageClass |
6 |
Start |
7 |
End |
8 |
IsRecurring |
Table 3 shows the default columns for a Table generated for the Contact folder.
Table 3. Default columns for tables in Contact folder
Column |
Property |
---|---|
1 |
EntryID |
2 |
Subject |
3 |
CreationTime |
4 |
LastModificationTime |
5 |
MessageClass |
6 |
FirstName |
7 |
LastName |
8 |
CompanyName |
Table 4 shows the default columns for a Table generated for the Task folder.
Table 4. Default columns for tables in Task folder
Column |
Property |
---|---|
1 |
EntryID |
2 |
Subject |
3 |
CreationTime |
4 |
LastModificationTime |
5 |
MessageClass |
6 |
DueDate |
7 |
PercentComplete |
8 |
IsRecurring |
You can specify the column within a row by specifying the Index of the Column. Index can be either a string representing the name of the Column or a 1-based integer value.
Table Code Sample
The following sample is available in the What's New Add-In that accompanies this article. The sample is displayed first in Microsoft Visual C#, and then in Microsoft Visual Basic .NET. The DemoTable procedure uses the Table object to populate a ListView control for the TableDialog form. The sample code also allows you to perform phrase-match searches against the Inbox to display a subset of items. For example, Figure 1 shows the TableDialog form displaying all items in my Inbox that contain the words "Dev" and "Con".
Figure 1. Use the Table object to build a user interface that contains Instant Search results
After you create a Table instance by calling Folder.GetTable, you call the Table.GetNextRow method to return a Row object and iterate in a forward-only manner through the Table. When Table.IsEndOfTable returns True, then you know that you have fetched all the available rows in the Table. The DemoTable procedure allows you to specify a filter that restricts the rows returned in the Table. The filter can be a valid JET or DAV Searching and Locating (DASL) query string. If the filter string is an empty string, then all rows in the folder are returned. See the following section, "Filtering the Table," for a detailed discussion about Outlook filter strings.
C# Example
string AddQuotes(string queryString)
{
StringBuilder sb = new StringBuilder();
sb.Append("\"");
sb.Append(queryString);
sb.Append("\"");
return sb.ToString();
}
void DemoTable(string filter)
{
try
{
Outlook.Table tbl = m_olApp.Session.GetDefaultFolder
(Outlook.OlDefaultFolders.olFolderInbox).GetTable
(filter, Outlook.OlTableContents.olUserItems);
// Remove the default column set.
tbl.Columns.RemoveAll();
// Add columns to the table.
tbl.Columns.Add("SenderName");
tbl.Columns.Add("Subject");
tbl.Columns.Add("ReceivedTime");
tbl.Columns.Add("EntryID");
tbl.Columns.Add("MessageClass");
tbl.Columns.Add("Unread");
// Sort by ReceivedTime in descending order.
tbl.Sort("ReceivedTime", true);
// Iterate the Table Rows.
while (!tbl.EndOfTable)
{
Outlook.Row nextRow = tbl.GetNextRow();
DateTime receivedTime = (DateTime)
nextRow["ReceivedTime"];
// Set properties on ListViewItem.
ListViewItem itm = new ListViewItem();
itm.Text = (string)nextRow["SenderName"];
itm.Tag = nextRow["EntryID"];
itm.SubItems.Add((string)nextRow["Subject"]);
// Format the DateTime.
itm.SubItems.Add(
receivedTime.ToString(@"ddd M/dd/yyyy hh:mm tt"));
string msgClass = (string)nextRow["MessageClass"];
itm.SubItems.Add(msgClass);
// MessageClass determines image key.
if (msgClass.Startswith("IPM.Note.Rules.OofTemplate"))
{
itm.ImageKey = "mail_oof";
}
else if (msgClass.Equals("IPM.Note"))
{
if ((bool)nextRow["UnRead"] == true)
{
itm.ImageKey = "MailUnread";
}
else
{
itm.ImageKey = "MailRead";
}
}
else if (msgClass.Equals(
"IPM.Schedule.Meeting.Resp.Pos"))
{
itm.ImageKey = "meeting-response-accept";
}
else if (msgClass.Equals(
"IPM.Schedule.Meeting.Resp.Neg"))
{
itm.ImageKey = "meeting-response-decline";
}
else if (msgClass.Equals(
"IPM.Schedule.Meeting.Resp.Tent"))
{
itm.ImageKey = "meeting-response-tentative";
}
else if (msgClass.Equals(
"IPM.Schedule.Meeting.Request"))
{
itm.ImageKey = "meeting-request";
}
else if (msgClass.Equals(
"IPM.Schedule.Meeting.Canceled"))
{
itm.ImageKey = "meeting-cancel";
}
// Add the ListViewItem to ListView.
ListView1.Items.Add(itm);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Visual Basic .NET Example
Function AddQuotes(ByVal queryString As String) As String
Dim sb As New StringBuilder
sb.Append(Chr(34))
sb.Append(queryString)
sb.Append(Chr(34))
Return sb.ToString
End Function
Sub DemoTable(ByVal filter As String)
Try
Dim tbl As Outlook.Table = m_olApp.Session.GetDefaultFolder _
(Outlook.OlDefaultFolders.olFolderInbox).GetTable _
(filter, Outlook.OlTableContents.olUserItems)
' Remove the default column set.
tbl.Columns.RemoveAll()
'Add columns to the table
tbl.Columns.Add("SenderName")
tbl.Columns.Add("Subject")
tbl.Columns.Add("ReceivedTime")
tbl.Columns.Add("EntryID")
tbl.Columns.Add("MessageClass")
tbl.Columns.Add("Unread")
' Sort by ReceivedTime in descending order.
tbl.Sort("ReceivedTime", True)
' Iterate the Table Rows.
Do Until tbl.EndOfTable
'Call GetNextRow
Dim nextRow As Outlook.Row = tbl.GetNextRow()
Dim receivedTime As DateTime = _
CType(nextRow("ReceivedTime"), Date)
' Set properties on ListViewItem.
Dim itm As New ListViewItem
itm.Text = nextRow("SenderName").ToString
itm.Tag = nextRow("EntryID").ToString
itm.SubItems.Add(nextRow("Subject").ToString)
' Format the DateTime.
itm.SubItems.Add _
(receivedTime.ToString("ddd M/dd/yyyy hh:mm tt"))
Dim msgClass As String = nextRow("MessageClass").ToString
itm.SubItems.Add(msgClass)
' MessageClass determines image key.
If msgClass.StartsWith("IPM.Note.Rules.OofTemplate") Then
itm.ImageKey = "mail_oof"
ElseIf msgClass.Equals("IPM.Note") Then
If CType(nextRow("UnRead"), Boolean) Then
itm.ImageKey = "MailUnread"
Else
itm.ImageKey = "MailRead"
End If
ElseIf msgClass.Equals("IPM.Schedule.Meeting.Resp.Pos") Then
itm.ImageKey = "meeting-response-accept"
ElseIf msgClass.Equals("IPM.Schedule.Meeting.Resp.Neg") Then
itm.ImageKey = "meeting-response-decline"
ElseIf msgClass.Equals("IPM.Schedule.Meeting.Resp.Tent") Then
itm.ImageKey = "meeting-response-tentative"
ElseIf msgClass.Equals("IPM.Schedule.Meeting.Request") Then
itm.ImageKey = "meeting-request"
ElseIf msgClass.Equals("IPM.Schedule.Meeting.Canceled") Then
itm.ImageKey = "meeting-cancel"
End If
' Add the ListViewItem to ListView.
ListView1.Items.Add(itm)
Loop
Catch ex As Exception
Debug.Print(ex.Message)
End Try
End Sub
Filtering the Table
A discussion of all aspects of filtering the Table is beyond the scope of this article. You can refer to the Outlook Developer Reference for more information. However, the important news for you as a developer is that you can take advantage of the Instant Search capabilities built into Outlook 2007. Instant Search is enabled by ci_startswith or ci_phrasematch keywords in your filter string. In order to use Instant Search keywords, you must create a DASL filter. Before I explain how to create a DASL filter, you first need to know about the two types of filter strings supported by the Table. The Table supports filter strings created in JET or DASL syntax. An explanation of both acronyms follows:
DASL. DAV Searching and Locating query syntax. In particular, the DASL mentioned in this article is based on the Microsoft Exchange implementation of DASL in Outlook.
JET. JET query syntax, based on Access Expression Service. JET refers to the Visual Basic–like language that is used to create filter strings for the Restrict method of the Items collection or Table object.
Table 5 lists the entry points for filter strings in the Outlook 2007 object model.
Table 5. Entry points for filter strings in Outlook 2007
Entry Point |
Description |
---|---|
Application.AdvancedSearch |
Only accepts DASL query without the prefix @SQL=. |
View.Filter |
Only accepts DASL query without the prefix @SQL=. |
Folder.GetTable, Table.FindRow, Table.Restrict |
Accepts DASL or JET query. DASL query must use the prefix @SQL=. |
Items.Find,Items.Restrict |
Accepts DASL or JET query. Using a DASL query for Items.Restrict is new to Outlook 2007. DASL query must use the prefix @SQL=. |
The following are supported content indexing (prefixed by CI_) string comparison keywords for DASL restrictions. Note that for the Items and Table objects, you can use content indexing in filters for Items.Restrict and Table.Restrict, but you cannot use content indexing keywords in filters for Items.Find and Table.FindRow. You cannot mix a DASL comparison and a JET comparison in the same filter string.
CI_STARTSWITH
CI_STARTSWITH performs a prefix-matching search. CI_STARTSWITH uses the characters, word, or words in the comparison string to match against the first few characters of any word within any indexed property:
The characters or word in the comparison must be what the match starts with.
The comparison is case-insensitive.
Prefixes in the subject of an item, such as RE: and FW:, are ignored in the match. If you need to determine items where the subject starts with RE:, then use 'RE:' in the comparison.
Any word within a multiple-word field, such as Subject or Message Body, can match. In these fields, the word does not have to be the first word.
CI_PHRASEMATCH
CI_PHRASEMATCH performs a phrase-match search. CI_PHRASEMATCH uses the characters, word, or words in the comparison string to match against entire words within any indexed property:
The characters or word in the comparison must be an exact match; no substring or prefix matching (for example, RE: or FW: in the subject)occurs.
The comparison is case-insensitive.
Subject prefixes, such as RE: and FW:, are ignored in phrase matching.
Any word within a multiple-word field, such as Subject or Message Body, can match. In these fields, the word does not have to be the first word.
CI_ Keywords Depend on Microsoft Windows Desktop Search
CI_STARTSWITH and CI_PHRASEMATCH use the Windows Desktop Search component to return Instant Search results. If the Windows Desktop Search component is not installed, you will receive an error if you attempt to restrict a Table using CI_ keywords. The Search component is an integral component of Microsoft Windows Vista. For Microsoft Windows XP SP2 and Microsoft Windows Server 2003, it must be installed as a separate component. You should use the IsInstantSearchEnabled property of the Store object to determine if Instant Search is enabled on a given Store. Your code should handle the possibility that Folder.Store.IsInstantSearchEnabled will return False. In that case, do not use CI_ keywords; use substring or equivalence matching instead.
LIKE
LIKE performs substring matching. If you use the LIKE keyword, your query will not use the Instant Search component and will not be as fast as a restriction using CI_STARTSWITH or CI_PHRASEMATCH. LIKE uses the characters or word in the comparison string to find a substring match. You must enclose the word or characters being matched in the comparison string with the % character. If the folder being searched is contained in a store that is not indexed, substring matching is used for all text comparisons:
The characters or word in the comparison can be anywhere within the words and properties.
The comparison is case-insensitive.
Enclose the characters or word in the comparison with the % character.
Any word within a multiple-word field, such as Subject or Message Body, can match; it does not have to be the first word.
LIKE comparison does not use the Instant Search engine. Substring matching using LIKE is slower than CI_STARTSWITH or CI_PHRASEMATCH.
=
= performs equivalence matching. If you use the = keyword, your query will not use content indexer search and will not be as fast as a search. = uses the characters or word in the comparison string to find an exact match:
The characters or word in the comparison must be equivalent to the words and properties being searched.
The comparison is case-insensitive.
Subject prefixes such as RE: and FW: are not ignored in equivalence matching with a DASL query. The DASL query operates against the MAPI property PR_SUBJECT rather than PR_NORMALIZED_SUBJECT. If you need to create an equivalence filter for an equivalence comparison, include the subject prefix in the comparison.
= comparison does not use the Instant Search engine. Equivalence matching using = is slower than CI_STARTSWITH or CI_PHRASEMATCH.
Example
Assume that the folder you are searching contains items with the following subjects:
Question
Questionable
Unquestionable
RE: Question
FW: Question
The big question
The following CI_STARTSWITH restriction returns the results listed:
string filter = "@SQL="
+ AddQuotes("urn:schemas:httpmail:subject")
+ " ci_startswith 'question'";
Question
Questionable
RE: Question
FW : Question
The big question
The following CI_PHRASEMATCH restriction returns the results listed:
string filter = "@SQL="
+ AddQuotes("urn:schemas:httpmail:subject")
+ " ci_phrasematch 'question'";
Question
RE: Question
FW : Question
The big question
The following like restriction returns the results listed:
string filter = "@SQL="
+ AddQuotes("urn:schemas:httpmail:subject")
+ " like '%question%'";
Question
Questionable
Unquestionable
RE: Question
FW: Question
The big question
The following = restriction will return the results listed:
string filter = "@SQL="
+ AddQuotes("urn:schemas:httpmail:subject")
+ " = 'question'";
Question
Additional Table Tips and Tricks
To help you be more productive with the Table object, following is a quick list of Table tips and tricks:
Use Table.Restrict to perform a restriction on the Rows in the Table. Restriction strings can be either DASL or JET queries. However, you cannot mix DASL and JET in the same query string.
Use Columns.Add to add columns in addition to the default Columns. You can add a column by its explicit built-in property name (such as Importance) or a name referencing the appropriate namespace, as discussed in the section "PropertyAccessor Object." You can also remove Columns from the Table if necessary. After you add a Column, use the same Column.Name to specify that column when you call Row[Index] to return a Column in the Row. Index can be either the name of the Column (as specified by the Name property of the Column) or a 1-based integer.
Certain computed properties such as DownloadState or BodyFormat cannot be added to the Table. Depending on the store provider, string properties added as a Column will return only the first 255 characters of the property. If you require the entire string, you can use the EntryID returned in the Table to open the full item by calling NameSpace.GetItemFromID. The PropertyAccessor object supports access to the entire string for properties that are not exposed by the Outlook object model.
Use Row.GetValues to return a 1-dimensional array of all values for a given row. Use Table.GetArray to return a 2-dimensional array of all column values for a specified number of rows.
Use Table.Sort to sort the rows in the Table. You can sort only by an explicit built-in property name such as Subject. If you need to sort by a name referencing a namespace, use Table.GetValues to return an array and sort the array.
The value returned for a given Column representing a DateTime value depends on whether an explicit built-in property name or a name referencing a namespace was used to specify the Column. For columns specified using an explicit built-in property name (such as LastModificationTime), the Row(Column) value is returned as local time. For columns specified using a name referencing a namespace (such as DAV:getlastmodified), the Row(Column) value is returned as Coordinated Universal Time (UTC). Use Row.LocalTimeToUTC and Row.UTCToLocalTime to perform conversions.
The value returned for a given column representing a binary (PT_BINARY) value depends on whether a built-in property name or a schema name was used to specify the Column. For explicit built-in property names (such as EntryID), the Row(Column) value is returned as a string. For property names referencing a namespace that represent a PT_BINARY property, the Row(Column) value is returned as a byte array. Use Row.BinaryToString to convert the byte array to a string.
If you call Folder.GetTable and set the TableContents argument to OlTableContents.olHiddenItems, the Rows returned in the Table will represent hidden items in the folder. You can restrict the hidden items just as you can restrict visible user items. Hidden items are typically used to store custom add-in preferences or Outlook settings such as auto-archive settings. You can also use Folder.GetStorage to create or get your own StorageItem. The StorageItem is designed for add-in settings that are hidden and that roam and are available online and offline.
PropertyAccessor Object
Like the Table object, the PropertyAccessor object represents a momentous change in what you can accomplish using the Outlook object model. The PropertyAccessor provides access to Outlook object properties that are not available through the Outlook object model. If you are familiar with Microsoft Collaboration Data Objects (CDO) 1.21, the PropertyAccessor is a replacement for the CDO Fields and Field objects. Unlike CDO, the PropertyAccessor uses a readable string value instead of an integer tag value to access both built-in and custom properties on Outlook objects. Here are the objects that support the PropertyAccessor:
AddressEntry
AddressList
AppointmentItem
Attachment
ContactItem
DistListItem
DocumentItem
ExchangeDistributionList
ExchangeUser
Folder
JournalItem
MailItem
MeetingItem
NoteItem
PostItem
Recipient
RemoteItem
ReportItem
SharingItem
Store
TaskItem
TaskRequestAcceptItem
TaskRequestDeclineItem
TaskRequestItem
TaskRequestUpdateItem
PropertyAccessor Sample Code
Let's take a look at an example that uses the PropertyAccessor object. When a message is delivered via Simple Mail Transfer Protocol (SMTP), the message is stamped with a transport header that contains information about the routing used to deliver the message to its destination. In previous versions of Outlook, developers would have to resort to CDO, Extended MAPI, or third-party libraries to read this property. In Outlook 2007, you can use the PropertyAccessor object to read the value of the transport header (PR_TRANSPORT_MESSAGE_HEADERS). The following code example shows you how to restrict the Table for messages where the transport header property is not Null, use NameSpace.GetItemFromID to open a Row in the Table, and then use the GetProperty method of the PropertyAccessor object to display the transport header in a message box.
C# Example
void DemoPropertyAccessorGetProperty()
{
string EntryID = "";
// SchemaName for PR_TRANSPORT_MESSAGE_HEADERS
string SchemaTransportHeader =
@"http://schemas.microsoft.com/mapi/proptag/0x007D001E";
string filter = "@SQL=" + "Not("
+ "\"" + SchemaTransportHeader + "\"" + " Is Null)";
Outlook.Table tbl = m_olApp.Session.GetDefaultFolder(
Outlook.OlDefaultFolders.olFolderInbox).GetTable(
filter, Outlook.OlTableContents.olUserItems);
if (tbl.GetRowCount() > 0)
{
Outlook.Row oRow = tbl.GetNextRow();
EntryID = oRow["EntryID"].ToString();
}
else
{
MessageBox.Show("No messages found with Transport Header.",
"Demo", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// Get MailItem using GetItemFromID.
Outlook.MailItem oMail =
(Outlook.MailItem)
m_olApp.Session.GetItemFromID(EntryID, Type.Missing );
// Obtain an instance of PropertyAccessor class.
Outlook.PropertyAccessor oPA = oMail.PropertyAccessor;
string Transport = (string)oPA.GetProperty(SchemaTransportHeader);
// Call GetProperty.
MessageBox.Show(this, Transport,
"Transport Header: " + oMail.Subject);
}
Visual Basic .NET Example
Sub DemoPropertyAccessorGetProperty()
Dim EntryID As String = ""
' SchemaName for PR_TRANSPORT_MESSAGE_HEADERS
Dim SchemaTransportHeader As String = _
"http://schemas.microsoft.com/mapi/proptag/0x007D001E"
Dim filter As String = "@SQL=" & "Not(" & Chr(34) _
& SchemaTransportHeader & Chr(34) & " Is Null)"
Dim tbl As Outlook.Table = m_olApp.Session.GetDefaultFolder( _
Outlook.OlDefaultFolders.olFolderInbox).GetTable( _
filter, Outlook.OlTableContents.olUserItems)
If tbl.GetRowCount > 0 Then
Dim oRow As Outlook.Row = tbl.GetNextRow()
EntryID = oRow("EntryID").ToString()
Else
MessageBox.Show("No messages found with Transport Header.", _
"Demo", MessageBoxButtons.OK, MessageBoxIcon.Information)
Exit Sub
End If
' Get MailItem using GetItemFromID.
Dim oMail As Outlook.MailItem = _
CType(m_olApp.Session.GetItemFromID(EntryID), Outlook.MailItem)
' Obtain an instance of PropertyAccessor class.
Dim oPA As Outlook.PropertyAccessor = oMail.PropertyAccessor
Dim Transport As String = _
CType(oPA.GetProperty(SchemaTransportHeader), String)
' Call GetProperty.
MessageBox.Show(Me, Transport, _
"Transport Header: " & oMail.Subject)
End Sub
Note |
---|
You might ask why the code example does not read the value of the PR_TRANSPORT_MESSAGE_HEADERS directly from the Table. The answer is that the length of strings returned in the Table is dependent on the folder's store provider. In the case of Exchange, the length of strings returned in the Table cannot exceed 255 bytes. What is the practical implication of this limitation? If you need strings that exceed 255 bytes for a folder in an Exchange store, then you should use the Table for restriction (in this case, providing rows for all items where the transport header property is not Null or Empty) and then use Namespace.GetItemFromID to obtain a full item. When you have a full item, the PropertyAccessor.GetProperty call will return the entire string rather than the truncated value that you would receive in the Table. |
Formats to Reference Properties by Namespace
The PropertyAccessor supports several formats for the string argument of a SetProperty or GetProperty call. While it is not possible to document all these formats in this article, the following list provides you with a quick guide to some of the supported formats that are valid for use with the PropertyAccessor. For more information, see the topic "Referencing Properties by Namespace" in the Outlook Developer Reference.
Property Tag Format
This format is based on the MAPI proptag namespace. Its syntax is as follows:
http://schemas.microsoft.com/mapi/proptag/0xHHHHHHHH
Where HHHHHHHH represents the hexadecimal representation of the MAPI property tag value. The Property Tag format raises an error if the value of 0xHHHHHHHH is above &H8000. Use formats listed under "Named Properties Format for the id Namespace" for properties where the hexadecimal tag value is greater than &H8000.
Examples:
http://schemas.microsoft.com/mapi/proptag/0x0037001E represents PR_SUBJECT.
http://schemas.microsoft.com/mapi/proptag/0x007D001E represents PR_TRANSPORT_MESSAGE_HEADERS.
Named Properties Format for the id Namespace
This format for named properties is based on the MAPI id namespace. Its syntax is as follows:
http://schemas.microsoft.com/mapi/id/{HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH}/HHHHHHHH
Where {HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH} represents the namespace GUID and HHHHHHHH represents the hexadecimal representation of the ID value. The PropertyAccessor method will perform an internal GetIDsFromNames to ensure that the correct property value is set or returned.
Example:
- http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/850E000B represents the NoAging property for the corresponding item.
Named Properties Format for the string Namespace
This format for named properties is based on the MAPI string namespace. Its syntax is as follows:
http://schemas.microsoft.com/mapi/string/{HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH}/name
Where {HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH} represents the namespace GUID and name represents the string name for the property. The PropertyAccessor method will perform an internal GetIDsFromNames to ensure that the correct property value is set or returned.
Examples:
http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/content-class
http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/x-mimeole
Office Format
The Microsoft Office Format represents a variation of the named properties format for the string namespace. The Office Format can also be used to refer to built-in Outlook properties. The following schema names should both return the categories for an Item.
http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/keywords
urn:schemas-microsoft-com:office:office#Keywords
Guidelines for PropertyAccessor Usage
The PropertyAccessor is a powerful object that requires some guidance for its use. The following list provides you with general guidelines for when it is appropriate to use the PropertyAccessor. For more information, see the topic "Best Practices for Getting and Setting Properties" in the Outlook Developer Reference.
The PropertyAccessor is not meant to replace object model properties such as MailItem.Subject or AppointmentItem.Start. For explicit built-in properties, you should continue to use the appropriate member of the object model. For custom properties added through UserProperties.Add or defined for a custom form, continue to use UserProperties(CustomPropertyName) to set or get the custom property.
When a given property is not exposed on an object, use the PropertyAccessor to read or set the property. The ability to set the property is dependent on a store provider. For example, some store providers do not allow you to set properties on the Folder object.
For objects that require an explicit Save (such as MailItem, ContactItem, and so on), you must call Save in order to persist properties that are set through the PropertyAccessor.
If you need to set or get multiple properties, use PropertyAccessor.GetProperties or PropertyAccessor.SetProperties to reduce network traffic and improve performance.
You can use the PropertyAccessor to create custom properties on Outlook items. These properties are not visible to a user through the field chooser and you cannot bind these custom properties automatically to a control on a custom form. If a property does not exist, calling SetProperty or SetProperties will create the specified property or properties.
The PropertyAccessor always returns date/time values in UTC time. If you need to convert a UTC DateTime value to local time, use the PropertyAccessor.UTCToLocalTime helper method.
The PropertyAccessor always returnsPT_BINARY values (used for Entry IDs) as a byte array. If you need to convert the byte array to a string, use the PropertyAccessor.BinaryToString helper method.
AddressEntry Enhancements
In line with the unification goal, the Outlook 2007 object model now supports displaying the address book programmatically and returning detailed information about AddressEntry objects, achieving parity with CDO in this respect. The SelectNamesDialog object lets you display the OutlookAddress Book dialog box and set options such as the number of buttons, button captions, and the initial address list.
SelectNamesDialog Object
As an example, assume that you need to display an Address Book dialog box that prompts the user for recipients in your Contacts folder. In previous versions of Outlook, you had to resort to CDO or third-party libraries in order to achieve this functionality. In Outlook 2007, you can display the Address Book by obtaining an instance of the SelectNamesDialog object. Here is a brief description of what the SelectNamesDialog object supports:
Setting the dialog box caption
Specifying the number of recipient selectors and changing the caption for a given selector
Setting or getting the initial address list for the dialog box and determining if the initial address list is the only address list available in the dialog box
Specifying whether users can select multiple recipients
Displaying the Outlook Address Book dialog box
Obtaining a Recipients collection object representing all the recipients that the user has selected in the dialog box
The SelectNamesDialog code sample displays an address book dialog box (shown in Figure 2) that lets the user select "Award Winner(s)" from their Contacts folder. If the user selects contacts, the SelectNamesDialog.Recipients object allows you to enumerate the selected recipients or use those recipients for additional functionality such as sending a message programmatically.
Figure 2. Customize the Outlook Address Book by using the SelectNamesDialog object
The sample code for SelectNamesDialog uses the Namespace.GetSelectNamesDialog method to create an instance of the SelectNamesDialog object. Before setting SelectNamesDialog.InitialAddressList, to ensure that a given AddressList is displayed as the initial address list, the code loops through the AddressLists collection and uses the new AddressList.GetContactsFolder method to ensure that the AddressList represents the default Contacts folder. After obtaining the correct AddressList and setting the InitialAddressList property, the code sets properties on the SelectNamesDialog object to specify the dialog box caption, number of recipient selectors, and selector command button captions. Finally, SelectNamesDialog.Recipients returns the selected recipients.
C# Example
void DemoAddressEntryEnhancements()
{
Outlook.AddressList ContactsAddrList = null;
Outlook.SelectNamesDialog oSND =
m_olApp.Session.GetSelectNamesDialog();
// First obtain the default Contacts folder.
string DefaultEntryID =
m_olApp.Session.GetDefaultFolder(
Outlook.OlDefaultFolders.olFolderContacts).EntryID;
// Enumerate AddressLists.
Outlook.AddressLists oAddrLists = m_olApp.Session.AddressLists;
foreach (Outlook.AddressList oAddrList in oAddrLists)
{
if (oAddrList.GetContactsFolder() != null)
{
// GetContactsFolder returns Folder object; compare EntryIDs.
if (m_olApp.Session.CompareEntryIDs(
oAddrList.GetContactsFolder().EntryID , DefaultEntryID))
{
// Set InitialAddressList to Contacts folder AddressList.
ContactsAddrList = oAddrList;
break;
}
}
else
{
MessageBox.Show("Could not find Contacts Address Book",
"Lookup Error", MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
}
// Set additional properties on SelectNamesDialog.
oSND.Caption = "Special Contest";
oSND.InitialAddressList = ContactsAddrList;
oSND.NumberOfRecipientSelectors =
Outlook.OlRecipientSelectors.olShowTo;
oSND.ToLabel="Award Winner(s)";
// Display.
oSND.Display();
// Enumerate names of selected award winners.
Outlook.Recipients oRecips = oSND.Recipients;
if (oRecips.Count > 0)
{
StringBuilder sb = new StringBuilder();
foreach(Outlook.Recipient oRecip in oRecips)
{
sb.AppendLine(oRecip.Name);
}
MessageBox.Show(this, sb.ToString(), "Selected Recipients");
}
}
Visual Basic .NET Example
Sub DemoAddressEntryEnhancements()
Dim ContactsAddrList As Outlook.AddressList = Nothing
Dim oSND As Outlook.SelectNamesDialog = _
m_olApp.Session.GetSelectNamesDialog
' First obtain the default Contacts folder.
Dim DefaultEntryID As String = _
m_olApp.Session.GetDefaultFolder _
(Outlook.OlDefaultFolders.olFolderContacts).EntryID
'Enumerate AddressLists
Dim oAddrLists As Outlook.AddressLists = _
m_olApp.Session.AddressLists
For Each oAddrList As Outlook.AddressList In oAddrLists
If Not (oAddrList.GetContactsFolder Is Nothing) Then
' GetContactsFolder returns Folder object; compare EntryIDs.
If m_olApp.Session.CompareEntryIDs _
(oAddrList.GetContactsFolder.EntryID, DefaultEntryID) Then
ContactsAddrList = oAddrList
Exit For
End If
End If
Next
If Not (ContactsAddrList Is Nothing) Then
' Set InitialAddressList to Contacts folder AddressList.
oSND.InitialAddressList = ContactsAddrList
Else
MessageBox.Show("Could not find Contacts Address Book", _
"Lookup Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End If
'Set additional properties on SelectNamesDialog
oSND.Caption = "Special Contest"
oSND.NumberOfRecipientSelectors = _
Outlook.OlRecipientSelectors.olShowTo
oSND.ToLabel = "Award Winner(s)"
' Display.
oSND.Display()
' Enumerate names of selected award winners.
Dim oRecips As Outlook.Recipients = oSND.Recipients
If oRecips.Count > 0 Then
Dim sb As New StringBuilder
For Each oRecip As Outlook.Recipient In oRecips
sb.AppendLine(oRecip.Name)
Next
MessageBox.Show(Me, sb.ToString, "Selected Recipients")
End If
End Sub
ExchangeUser and ExchangeDistributionList Objects
Now that you can display an Outlook address book programatically, you might wonder how you can discover additional information about the selected recipients. If the recipient is an SMTP address, there is not much you can do beyond finding the recipient's display name and SMTP address. However, if the recipient is an Exchange recipient, the Outlook 2007 object model just made your life a whole lot easier. You can use two new objects that derive from the legacy AddressEntry object:
ExchangeUser
ExchangeDistributionList
These objects derive from the legacy AddressEntry object. Unlike the legacy AddressEntry object, they provide full programmatic details on an AddressEntry that represents an Exchange user or an Exchange distribution list. You can get properties on these objects such as PrimarySMTPAddress, LastName, FirstName, and so on. You can also determine the manager of the ExchangeUser or obtain an AddressEntries collection that represents all the direct reports for the given ExchangeUser.
The following code sample shows how to get the first item in your Inbox where the sender is an Exchange AddressEntry (to be exact, where the AddressEntry.Type = 'EX'). If an item is found that satisfies the query, the PropertyAccessor is used to obtain the ContactItem.EntryID of the sender. The EntryID is passed to the new GetAddressEntryFromID method on the NameSpace object to return a unique AddressEntry. If the AddressEntryUserType of the AddressEntry indicates that the AddressEntry represents an Exchange user, you call AddressEntry.GetExchangeUser to return an ExchangeUser object. After you have an ExchangeUser object, you can get properties such as Department, BusinessTelephoneNumber, or JobTitle to use in your code. In the following code sample, these properties for the Exchange sender are displayed in a message box.
C# Example
void DemoSenderDetails()
{
string SchemaSenderEntryID =
@"http://schemas.microsoft.com/mapi/proptag/0x0C190102";
// Get first item in the Inbox.
Outlook.Items oItems = m_olApp.Session.GetDefaultFolder(
Outlook.OlDefaultFolders.olFolderInbox).Items;
oItems.Sort("ReceivedTime",true);
Outlook.MailItem oMail = (Outlook.MailItem)oItems.Find(
"[MessageClass]='IPM.Note' And [SenderEmailType] = 'EX'");
if (oMail != null)
{
string SenderEntryID =
oMail.PropertyAccessor.BinaryToString(
oMail.PropertyAccessor.GetProperty(SchemaSenderEntryID));
Outlook.AddressEntry oSender =
m_olApp.Session.GetAddressEntryFromID(SenderEntryID);
// Now we have an AddressEntry representing the Sender.
if(oSender.AddressEntryUserType ==
Outlook.OlAddressEntryUserType.olExchangeUserAddressEntry
|| oSender.AddressEntryUserType ==
Outlook.OlAddressEntryUserType.
olExchangeRemoteUserAddressEntry)
{
// Use the ExchangeUser object for Sender details.
Outlook.ExchangeUser oExchUser =
oSender.GetExchangeUser();
StringBuilder sb = new StringBuilder();
sb.AppendLine(oExchUser.Name);
sb.AppendLine(oExchUser.JobTitle);
sb.AppendLine(oExchUser.Department);
sb.AppendLine(oExchUser.BusinessTelephoneNumber);
MessageBox.Show(this, sb.ToString(),
"Sender Details",MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
}
MessageBox.Show(this, "Exchange sender not found.",
"Sender Details", MessageBoxButtons.OK,
MessageBoxIcon.Warning);
}
}
Visual Basic .NET Example
Sub DemoSenderDetails()
Const SchemaSenderEntryID As String = _
"http:schemas.microsoft.com/mapi/proptag/0x0C190102"
' Get first item in the Inbox.
Dim oItems As Outlook.Items = m_olApp.Session.GetDefaultFolder _
(Outlook.OlDefaultFolders.olFolderInbox).Items
oItems.Sort("ReceivedTime", True)
Dim oMail As Outlook.MailItem = CType(oItems.Find _
("[MessageClass]='IPM.Note' And [SenderEmailType] = 'EX'"), _
Outlook.MailItem)
If Not (oMail Is Nothing) Then
Dim SenderEntryID As String = _
oMail.PropertyAccessor.BinaryToString _
(oMail.PropertyAccessor.GetProperty(SchemaSenderEntryID))
Dim oSender As Outlook.AddressEntry = _
m_olApp.Session.GetAddressEntryFromID(SenderEntryID)
' Now we have an AddressEntry representing the Sender.
If oSender.AddressEntryUserType = _
Outlook.OlAddressEntryUserType _
.olExchangeUserAddressEntry Or _
oSender.AddressEntryUserType = _
Outlook.OlAddressEntryUserType _
.olExchangeRemoteUserAddressEntry Then
' Use the ExchangeUser object for Sender Details.
Dim oExchUser As Outlook.ExchangeUser = _
oSender.GetExchangeUser
Dim sb As StringBuilder = New StringBuilder()
sb.AppendLine(oExchUser.Name)
sb.AppendLine(oExchUser.JobTitle)
sb.AppendLine(oExchUser.Department)
sb.AppendLine(oExchUser.BusinessTelephoneNumber)
MessageBox.Show(Me, sb.ToString, "Sender Details", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
Exit Sub
End If
End If
MessageBox.Show(Me, "Exchange sender not found.", _
"Sender Details", MessageBoxButtons.OK, _
MessageBoxIcon.Warning)
End Sub
Rules Objects
Rules are one of the most powerful features of Outlook for staying organized and responding to the continuous flow of messages into your Inbox. In previous versions of Outlook, developers were not allowed to change rules. In Outlook 2007, you can programmatically create rules, determine conditions or exceptions that determine whether the rule will run, and set actions to occur. Typical rule actions are moving or copying an item to a folder, setting one or more categories on the item, or marking the item as a task. Items marked as a task appear in your To-Do Bar, and this action represents an important new feature in Outlook 2007. You can also establish rule conditions or exceptions that determine whether or not the rule will execute. Typical rule conditions are whether the message is from or to a specific recipient, whether you are on the To or Cc line of a message, whether the message contains specific words in the body or subject, or whether the item is from a specified RSS feed.
The Rules objects are simple to use and can be used to create rules programmatically for your solution. Here is a quick guide to the Rules objects in the Outlook 2007 object model:
All rules defined for the logged-on session are contained in the Rules collection object. Obtain the Rules collection object by calling GetRules on the DefaultStore of the NameSpace object.
You can enumerate the rules in the Rules collection. When you enumerate Rules, you can enable or disable the rule programmatically.
To create a new Rule, call Rules.Create. When you call the Create method, you must specify a RuleType argument. A rule can be of type OlRuleType.olRuleReceive or OlRuleType.olRuleSend. These enumeration constants correspond to send and receive rules in the Outlook Rules Wizard.
After you have an instance of a new Rule object, you can programmatically enable Rule.Actions, Rule.Conditions, and Rule.Exceptions. Each of these objects represents a static collection object. You cannot programmatically add your own rule actions, for example, to the RuleActions collection object.
Some rule actions or conditions can only be enabled or disabled. For example, you can only enable or disable the rule condition of type olConditionOnlyToMe. Other rule conditions require that you set additional properties on the rule condition. In the following sample code, you must add one or more recipients to the ToOrFromRuleCondition.Recipients collection object.
After you have finished setting rule actions, conditions, and exceptions, you should call Rules.Save to persist the newly created Rule.
You can also call Rule.Execute to run the rule programmatically. You do not have to save the rule in order to call Rule.Execute.
Rules Sample Code
The following sample code demonstrates the Rules object model in action. The following example creates a rule that fires after a message is received. The conditions on the rule are that the message is from "someone@example.com" and that the message is addressed only to me. If the rule conditions are met, the message is marked for follow-up today so that it will appear in the To-Do Bar.
C# Example
void DemoCreateMarkAsTaskRule()
{
// Obtain Rules from DefaultStore.
Outlook.Rules oRules = m_olApp.Session.DefaultStore.GetRules();
// Create new ReceiveRule.
Outlook.Rule oRule =
oRules.Create("MarkAsTaskRule",
Outlook.OlRuleType.olRuleReceive);
// Enable From and OnlyToMe Conditions and set properties.
oRule.Conditions.From.Enabled = true;
oRule.Conditions.From.Recipients.Add("someone@example.com");
oRule.Conditions.From.Recipients.ResolveAll();
oRule.Conditions.OnlyToMe.Enabled = true;
//En able MarkAsTask Action and set properties.
oRule.Actions.MarkAsTask.Enabled = true;
oRule.Actions.MarkAsTask.FlagTo = "Follow up";
oRule.Actions.MarkAsTask.MarkInterval =
Outlook.OlMarkInterval.olMarkToday;
// Save the Rules collection.
oRules.Save(false);
}
Visual Basic .NET Example
Sub DemoCreateMarkAsTaskRule()
' Obtain Rules from DefaultStore.
Dim oRules As Outlook.Rules = _
m_olApp.Session.DefaultStore.GetRules
' Create new Receive Rule.
Dim oRule As Outlook.Rule = _
oRules.Create("MarkAsTaskRule", Outlook.OlRuleType.olRuleReceive)
' Enable From and OnlyToMe Conditions and set properties.
oRule.Conditions.From.Enabled = True
oRule.Conditions.From.Recipients.Add("someone@example.com")
oRule.Conditions.From.Recipients.ResolveAll()
oRule.Conditions.OnlyToMe.Enabled = True
' Enable MarkAsTask Action and set properties.
oRule.Actions.MarkAsTask.Enabled = True
oRule.Actions.MarkAsTask.FlagTo = "Follow up"
oRule.Actions.MarkAsTask.MarkInterval = _
Outlook.OlMarkInterval.olMarkToday
' Save the Rules collection.
oRules.Save(ShowProgress:=False)
End Sub
Sharing Objects
Outlook 2007 makes sharing information with others easier than it has been in previous versions. There are a variety of sharing mechanisms such as Exchange public folders and Microsoft Windows SharePoint Services sites. I will not focus on these areas because they deserve an entire article (or even a book) about enterprise sharing of information in Office 2007. In this section, I focus on sharing personal information such as Contacts or Calendar. You can share this personal information with coworkers or with friends and family. I focus on one specific scenario, and show you how to accomplish calendar sharing programmatically. Along the way, learn how sharing objects play an important new role in the Outlook 2007 object model.
Here is a quick look at several sharing objects that you can use in your solutions:
SharingItem object, which lets you programmatically send sharing messages in an Exchange environment
CalenderSharing object, which lets you export a calendar date range to an iCal attachment
OpenSharedFolder method on the NameSpace object, which lets you open several sharing resources, including a WebCal URL, RSS URL, or a URL to a Windows SharePoint Services site
OpenSharedItem method on the NameSpace object, which lets you open an item in Outlook .msg format, vCard format, or iCal format
CalendarSharing Sample Code
Let's assume that your users periodically want to share their calendars with an important external customer. You could rely on the users to create a payload-calendar-sharing item through the Outlook user interface on a periodic basis. Because you are a developer, you can also automate this scenario through the new sharing objects in Outlook 2007. The following code sample uses the CalendarSharing object to create an iCal payload-sharing item with full details for one week. The item is addressed to someone@example.com and then saved to the Drafts folder.
C# Example
void DemoCalendarSharing()
{
// Get instance of CalendarSharing object.
Outlook.CalendarSharing oShare =
m_olApp.Session.GetDefaultFolder
(Outlook.OlDefaultFolders.olFolderCalendar)
.GetCalendarExporter();
// Full details.
oShare.CalendarDetail =
Outlook.OlCalendarDetail.olFullDetails;
// Obtain start and end dates.
DateTime StartDate = DateTime.Now;
DateTime EndDate = StartDate.AddDays(7);
oShare.StartDate = StartDate;
oShare.EndDate = EndDate;
// Call ForwardAsICal method.
Outlook.MailItem oMail =
oShare.ForwardAsICal(Outlook.OlCalendarMailFormat
.olCalendarMailFormatDailySchedule);
// Add recipient.
oMail.Recipients.Add("someone@example.com");
oMail.Recipients.ResolveAll();
// Set subject.
string CalName = m_olApp.Session.GetDefaultFolder
(Outlook.OlDefaultFolders.olFolderCalendar).Name;
oMail.Subject = m_olApp.Session.CurrentUser.Name +
CalName.PadLeft(CalName.Length+1);
// Save calendar sharing item.
oMail.Save();
}
Visual Basic .NET Example
Sub DemoCalendarSharing()
' Get instance of CalendarSharing object.
Dim oShare As Outlook.CalendarSharing = _
m_olApp.Session.GetDefaultFolder _
(Outlook.OlDefaultFolders.olFolderCalendar) _
.GetCalendarExporter()
' Full details.
oShare.CalendarDetail = _
Outlook.OlCalendarDetail.olFullDetails
' Obtain Start and End dates.
Dim StartDate As Date = DateTime.Now
Dim EndDate As Date = StartDate.AddDays(7)
oShare.StartDate = StartDate
oShare.EndDate = EndDate
' Call ForwardAsICal method.
Dim oMail As Outlook.MailItem = _
oShare.ForwardAsICal(Outlook.OlCalendarMailFormat _
.olCalendarMailFormatDailySchedule)
' Add recipient.
oMail.Recipients.Add("someone@example.com")
oMail.Recipients.ResolveAll()
'Set subject
Dim CalName As String = m_olApp.Session.GetDefaultFolder _
(Outlook.OlDefaultFolders.olFolderCalendar).Name
oMail.Subject = m_olApp.Session.CurrentUser.Name _
& CalName.PadLeft(CalName.Length + 1)
' Save calendar sharing item.
oMail.Save()
End Sub
Context Menus
Programmable context menus have been one of the top developer requests for several versions of Outlook. In Outlook 2007, you can customize the following context menus:
Attachment context menu
Item context menu (for an item in a view)
Folder context menu (for a folder in the folder tree)
Shortcut context menu
View context menu
The What's New Add-In provides sample code to get you started on context-menu customization. Outlook fires an event when a context menu is about to be displayed. The ItemContextMenuDisplay event passes a CommandBar object that represents the context menu controls on the context menu. In the event procedure, you have the opportunity to add new commands to the context menu or modify existing commands. You can do all of this through the familiar Office CommandBars object model. These commands are not persisted by Outlook. Every time the event fires, you must perform your command-bar modifications.
The What's New Add-In places an Instant Search command at the bottom of an item's context menu, as shown in Figure 3. Instant Search is a pop-up command bar control that lets the user take advantage of the Outlook Instant Search feature and display messages from a sender that were sent last week, this week, last month, this month, or all messages. The search results are displayed in a separate Explorer window.
The code for context-menu customization is not included in this article due to space limitations. But if you are familiar with the CommandBars object model, writing context menu customizations in Outlook 2007 should be easy for you.
Figure 3. Add custom context menu items using new Application-level events
Conclusion
This article only begins to uncover what you can accomplish with Outlook 2007 extensibility. This is the most programmable version of Outlook ever released, and we attempted to remove most of the obstacles that stood in your way in previous versions. Outlook 2007 allows you to build rich new solutions using form regions, Custom Task Panes, and the Ribbon. Start with the What's New Add-Ins to give yourself a quick lesson in new object model features such as the Table and PropertyAccessor. If you are developing a managed add-in, take a look at the new version of Microsoft Visual Studio Tools for the Microsoft Office System (Visual Studio Tools for Office) on the Microsoft Visual Studio Developer Center. With designers for special Office 2007 development areas such as the Ribbon or Custom Task Panes, Visual Studio Tools for Office provides the optimal development environment for Outlook 2007.
After you familiarize yourself with the object model and select your development environment, download the Outlook 2007 sample add-ins available on MSDN. These add-ins cover almost all areas of Outlook 2007 extensibility, and include code for the new Table, PropertyAccessor, FormRegion, ExchangeUser, ExchangeDistributionList, SharingItem, and StorageItem objects. You will learn how to implement the IRibbonExtensibility, ICustomTaskPaneConsumer, and FormRegionStartup interfaces in order to customize the Ribbon, Custom Task Panes, and form regions. Finally, build your solution using the great new platform capabilities in Outlook 2007 and the 2007 Microsoft Office system. We have attempted to make Outlook 2007 development a more exciting and productive experience. Please create some fantastic new solutions for your customers, and have fun along the way!