Searching the Outlook 2007 Inbox for Items with Subjects Containing Specific Words

Office Visual How To

Applies to: 2007 Microsoft Office System, Microsoft Office Outlook 2007, Visual Studio 2005, Visual Studio 2005 Tools for Office Second Edition

Ken Getz, MCW Technologies, LLC

May 2007

Overview

Your Outlook 2007 inbox may contain many messages, and you may want to filter the messages so that you can find specific keywords in the subjects. Outlook provides several different searching and filtering mechanisms. This article discusses two specific techniques: calling a folder's GetTable method by using a filter that returns the rows you want; and calling the Outlook Application object's AdvancedSearch method by using a folder reference and a query string. Each of these techniques uses the DAV Searching and Locating (DASL) syntax to specify the filter or query string. The example code searches the Subject field of mail messages to find a subset of the messages in the Inbox folder.

See It Searching the Outlook 2007 Inbox video

Watch the Video

Length: 10:05 | Size: 7.95 MB | Type: WMV file

Code It | Read It | Explore It

Code It

In this demonstration, you create Office Fluent Ribbon customization that adds two buttons that are available when you compose an e-mail message. This location is arbitrary, but it allows you to test the sample code.

To create the Office Fluent Ribbon customization

  1. In Visual Studio 2005, create a new Visual Studio 2005 Second Edition add-in for Outlook 2007. Name the new project SearchInboxAddIn.

  2. In the Solution Explorer window, right-click the project, select Add, and then select New Item.

  3. In the Add New Item dialog box, select Ribbon Support. Click Add to insert the new Office Fluent Ribbon customization.

  4. Modify the new Ribbon1.xml file, replacing the existing content with the following XML.

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" 
    onLoad="OnLoad">
  <ribbon>
    <tabs>
      <tab idMso="TabAddIns">
        <group id="MyGroup"
             label="Filter">
          <button id="button1"
                  label="GetTable"
                  screentip="Use the GetTable method to filter"
                  onAction="OnClick"
                  />
          <button id="button2"
                  label="AdvancedSearch"
                  screentip="Use the AdvancedSearch method to filter"
                  onAction="OnClick"
                  />
        </group>
      </tab>
    </tabs>
  </ribbon>
</customUI>

To save the Ribbon1.xml file as a project resource

  1. In the Solution Explorer window, right-click the SearchInboxAddIn project, and select Properties.

  2. In the Properties pane, select the Resources tab.

  3. From the Solution Explorer window, drag the Ribbon1.xml file into the Properties pane.

    This adds the Ribbon1.xml file as a project resource.

  4. Close the Properties pane, and click Yes when prompted to save.

  5. In the Ribbon1.vb or Ribbon1.cs file, uncomment the ThisAddIn partial class.

  6. In C# only, at the top of the code file, add the following using statement.

using Outlook=Microsoft.Office.Interop.Outlook;

To modify GetCustomUI

  • In the Ribbon1 class, modify the existing GetCustomUI implementation so that it retrieves the Ribbon1 resource, and only returns the resource if the current ribbonID value is Microsoft.Outlook.Mail.Compose. In C#, you must expand the IRibbonExtensibility members code region to find the procedure.
Public Function GetCustomUI(ByVal ribbonID As String) As String _
 Implements Office.IRibbonExtensibility.GetCustomUI

  If ribbonID = "Microsoft.Outlook.Mail.Compose" Then
    Return My.Resources.Ribbon1
  Else
    Return String.Empty
  End If
End Function
public string GetCustomUI(string ribbonID)
{
  if (ribbonID == "Microsoft.Outlook.Mail.Compose")
  {
    return Properties.Resources.Ribbon1;
  }
  else
  {
    return String.Empty;
  }
}

To add a declaration

  • In the Ribbon1 class, add the following declaration, making it simpler to refer to the Outlook Application object.
Private Application As Outlook.Application = _
  Globals.ThisAddIn.Application
private Outlook.Application Application = 
  Globals.ThisAddIn.Application;

To add a callback procedure

  • In the Ribbon1 class, add the following callback procedure so that clicking buttons on the Ribbon calls this procedure.
Public Sub OnClick(ByVal control As Office.IRibbonControl)
  Select Case control.Id
    Case "button1"
    Case "button2"
  End Select
End Sub
public void OnClick(Office.IRibbonControl control)
{
  switch (control.Id)
  {
    case "button1":
      break;
    case "button2":
      break;
  }
}

To verify the new buttons and add two constants

  1. Save the project, and run it. In Outlook 2007, create a new e-mail message. On the Ribbon, click the Add-Ins tab, and verify that you see the two new buttons on the Ribbon. Close Outlook when you are done, returning to Visual Studio 2005.

  2. In the Ribbon1 class, add these two constants. The first contains text you use whenever you create a property reference using DASL syntax, and the second contains the MAPI property identifier corresponding to the PR_SUBJECT property.

Const PROPTAG As String = "http://schemas.microsoft.com/mapi/proptag/"
Const PR_SUBJECT As String = "0x0037001E"
const String PROPTAG = "http://schemas.microsoft.com/mapi/proptag/";
const String PR_SUBJECT = "0x0037001E";

To add search results to the e-mail message

  • Add the following procedure to the Ribbon1 class.

    This procedure iterates through all the rows in the table it passes, adding information about the Subject field and the From field in each row to the body of the current e-mail message.

    NoteNote

    This procedure starts by removing all the columns from the table schema.

    By default, an Outlook Table instance only includes columns that are pertinent to all item types. If you want to view data for a specific item type (in this case, an e-mail message), you must explicitly add the necessary columns to the table. To ensure that no columns are repeated, the code clears the columns first, and then adds the necessary columns manually. The code then uses the GetNextRow method of the Table instance to iterate through all the rows, retrieving the two column values. Finally, the code inserts the results into the body of the current e-mail message.

Sub DisplayTableContents(ByVal table As Outlook.Table)
  ' Add a new column, and reset the current 
  ' row so that it is immediately before the
  ' first row of data in the table:
  table.Columns.RemoveAll()
  table.Columns.Add("Subject")
  table.Columns.Add("From")

  Dim sw As New StringWriter
  Dim row As Outlook.Row

  While Not table.EndOfTable
    row = table.GetNextRow
    sw.WriteLine("{0} from {1}", _
     row("Subject"), row("From"))
  End While

  ' Assuming that you are working with a mail item,
  ' cast the current item as a mail item, and insert
  ' the data into the body:
  Dim mailItem As Outlook.MailItem = _
   TryCast(Application.ActiveInspector.CurrentItem, _
   Outlook.MailItem)
  If mailItem IsNot Nothing Then
    mailItem.Body = sw.ToString() & mailItem.Body()
  End If
End Sub
void DisplayTableContents(Outlook.Table table)
{
  // Add a new column, and reset the current 
  // row so that it is immediately before the
  // first row of data in the table:
  table.Columns.RemoveAll();
  table.Columns.Add("Subject");
  table.Columns.Add("From");

  StringWriter sw = new StringWriter();
  Outlook.Row row = null;

  while (!table.EndOfTable)
  {
    row = table.GetNextRow();
    sw.WriteLine("{0} from {1}",
     row["Subject"], row["From"]);
  }

  // Assuming that you are working with a mail item,
  // cast the current item as a mail item, and insert
  // the data into the body:
  Outlook.MailItem mailItem =
   Application.ActiveInspector().CurrentItem as Outlook.MailItem;
  if (mailItem != null)
  {
    mailItem.Body = sw.ToString() + mailItem.Body;
  }
}

To use the GetTable method

  • To demonstrate the GetTable method of an Outlook folder, add the following procedure to the Ribbon1 class.

    This procedure calls the DisplayTableContents procedure you just created to add the results to the body of the e-mail message. The code creates a filter string using DASL syntax (see the Read It section for more information about this mechanism). The Folder.GetTable method retrieves only the rows you request, honoring the filter you pass as a parameter to the method.

Sub GetFilteredTable(ByVal searchText As String)
  Dim filter As String = _
    String.Format("@SQL=""{0}{1}"" ci_phrasematch '{2}'", _
    PROPTAG, PR_SUBJECT, searchText)

  Try
    Dim folder As Outlook.Folder = _
     TryCast(Application.Session.GetDefaultFolder( _
     Outlook.OlDefaultFolders.olFolderInbox), Outlook.Folder)

    If (folder IsNot Nothing) Then
      DisplayTableContents(folder.GetTable(filter))
    End If
  Catch ex As Exception
    MessageBox.Show(ex.Message)
  End Try
End Sub
void GetFilteredTable(string searchText)
{
  String filter =
    String.Format("@SQL=\"{0}{1}\" ci_phrasematch '{2}'",
    PROPTAG, PR_SUBJECT, searchText);

  Outlook.Folder folder =
    Application.Session.GetDefaultFolder(
    Outlook.OlDefaultFolders.olFolderInbox) as Outlook.Folder;

  if (folder != null)
  {
    try
    {
      DisplayTableContents(
        folder.GetTable(filter, 
        Outlook.OlTableContents.olUserItems));
    }
    catch (Exception ex)
    {
      MessageBox.Show(ex.Message);
    }
  }
}

To use the Application.AdvancedSearch method

  • To demonstrate the Application.AdvancedSearch method, add the following procedure to the Ribbon1 class.

    This procedure sets up a query string, this time using a slightly simpler formatting—the AdvancedSearch method does not require you to specify the @SQL identifier or to place the property information in quotation marks. The Application.AdvancedSearch method performs its work asynchronously. To retrieve the results of the search, you place the code in the Application.AdvancedSearchComplete event handler. This code starts the search going, and then completes.

Sub UseAdvancedSearch(ByVal searchText As String)
  Dim query As String = _
    String.Format("{0}{1} ci_phrasematch '{2}'", _
    PROPTAG, PR_SUBJECT, searchText)

  ' Perform the search, asynchronously.
  Application.AdvancedSearch( _
   "Inbox", query, False, "TestTag")
End Sub
void UseAdvancedSearch(string searchText)
{
  String query =
    String.Format("{0}{1} ci_phrasematch '{2}'",
    PROPTAG, PR_SUBJECT, searchText);

  // Perform the search, asynchronously.
  Application.AdvancedSearch(
   "Inbox", query, false, "TestTag");
}

To create a handler for the Application object's AdvancedSearchComplete event

  • Add the following procedure to the Ribbon1 class to handle the event. Because the AdvancedSearch method raises an event after it completes the search, you need a handler for the Application object's AdvancedSearchComplete event.

    This code retrieves the requested table, and passes it to the common DisplayTableContents procedure.

    NoteNote

    This code also removes the event handler after it finishes, so you do not end up adding multiple event handlers for the same event by running the code multiple times.

Sub Application_AdvancedSearchComplete( _
 ByVal searchObject As Outlook.Search)
  ' Although there is no chance a different search
  ' could come through here in this example, it is worth
  ' testing, just in case.
  If searchObject.Tag = "TestTag" Then
    DisplayTableContents(searchObject.GetTable)
  End If

  ' Remove the handler, so you do not end up with multiple
  ' handlers for the same event. 
  RemoveHandler Application.AdvancedSearchComplete, _
    AddressOf Application_AdvancedSearchComplete
End Sub
void Application_AdvancedSearchComplete(
  Outlook.Search searchObject)
{
  // Although there is no chance a different search
  // could come through here in this example, it is worth
  // testing, just in case.
  if (searchObject.Tag == "TestTag")
  {
    DisplayTableContents(searchObject.GetTable());
  }

  // Remove the handler, so you do not end up with multiple
  // handlers for the same event. 
  Application.AdvancedSearchComplete -=
    Application_AdvancedSearchComplete;
}

To modify the existing OnClick procedure

  • Modify the existing OnClick procedure, so that it now calls the two example procedures that you have created. In addition, the procedure hooks up the AdvancedSearchComplete event handler. Replace the search keyword ("Office") with a term you are likely to find in your own Inbox folder.
Public Sub OnClick(ByVal control As Office.IRibbonControl)
  Select Case control.Id
    Case "button1"
      GetFilteredTable("Office")
    Case "button2"
      AddHandler Application.AdvancedSearchComplete, _
       AddressOf Application_AdvancedSearchComplete
      UseAdvancedSearch("Office")
  End Select
End Sub
public void OnClick(Office.IRibbonControl control)
{
  switch (control.Id)
  {
    case "button1":
      GetFilteredTable("Office");
      break;

    case "button2":
      Application.AdvancedSearchComplete += 
        Application_AdvancedSearchComplete;
      UseAdvancedSearch("Office");
      break;
  }
}

Save and run the application. In Outlook, create an e-mail message, click the Add-Ins tab, and try each of the two sample buttons. Verify that each time you add information about the subject and sender of each message, that it contains the keyword you supply within the subject of the e-mail message.

Read It

The Outlook object model provides several different techniques that you can use to search for and retrieve filtered data. The Search.Results object provides for flexible searching, but the Table object is more lightweight and, therefore, provides better performance.

Use the Folder.GetTable method if you need to stop and wait for the results. If you want to retrieve the results asynchronously, use the Application.AdvancedSearch method and handle the Application.AdvancedSearchComplete event, as shown in this demonstration.

You should also be aware of a few issues surrounding the sample code shown here.:

  • The Folder.GetTable method and Application.AdvancedSearch method perform a case-insensitive match against the criterion you supply.

  • The Folder.GetTable method expects you to supply a filter expression in a specific format, like the following.

    @SQL="http://schemas.microsoft.com/mapi/proptag/0x0037001E" 
      ci_phrasematch 'Office'
    

    The first part of the expression specifies a MAPI schema indicating that you are specifying a MAPI property, and 0x0037001E is the specific value corresponding to the PR_SUBJECT MAPI property. The ci_phrasematch performs a search anywhere within the specified property for a match against the string that follows. (You might also want to investigate the ci_startswith keyword, which allows you to match against the first part of a property value.)

  • The Application.AdvancedSearch method expects you to specify the same type of query string, but in a slightly simpler format.

    http://schemas.microsoft.com/mapi/proptag/0x0037001E 
      ci_phrasematch 'Office'
    

Explore It