(Less Than) Portable Search Folders

Let’s have fun with the object model and search folders:

  1. Outlook running a profile in online mode.

  2. Create this macro:

    Sub TestAdvancedSearchComplete()

        Dim sch As Outlook.Search

        set sch = Application.AdvancedSearch("Inbox", "urn:schemas-microsoft-com:office:office#Keywords like 'Test'", True, "MySearchFolder")

        sch.Save ("MySearchFolder")

    End Sub

  3. Run the macro, observe the folder.

  4. On the same machine, create a new profile in cached mode and start Outlook

  5. Run macro, get “Run-time error '-2147219964 (80040604)': Cannot create folder.”

If we want to have more fun, instead of running the macro the second time, we can locate the folder “MySearchFolder” under the Search Folder node in Outlook. If we click on it, Outlook creates a search folder, but it doesn’t populate with the items we expect to find.

What’s going on here? To answer that, we have to know a bit about how Outlook’s persisted search folder feature works. When you create a search folder, either using the Object Model, or directly in Outlook, the folder isn’t the first thing created. The first thing created is the search folder definition message. This is a message with the message class “IPM.Microsoft.WunderBar.SFInfo” which lives in the associated contents of the Common Views folder. We can use MFCMAPI to take a look at this message:

Our search folder definition message from Common Views

And we can use MFCMAPI’s Smart View feature to parse the search folder definition stored in PR_WB_SF_DEFINITION:

PR_WB_SF_DEFINITION, with parsing courtesy Smart View

From this, we find that the search folder definition contains the restriction that will form the basis of the search folder:

 Restriction:
 lpRes->rt = RES_CONTENT
 lpRes->res.resContent.ulFuzzyLevel = FL_IGNORECASE | FL_FULLSTRING = 0x00010000
 lpRes->res.resContent.ulPropTag = 0x8010101F (PT_MV_UNICODE)
 lpRes->res.resContent.lpProp->ulPropTag = 0x8010001F (PT_UNICODE)
 lpRes->res.resContent.lpProp->Value = Test
   Alt: cb: 8 lpb: 5400650073007400

Note that this restriction includes a property in the 0x8000 range, meaning it’s a named property. In fact, it’s the named property “Keywords” in the PS_PUBLIC_STRINGS namespace. Note also that there’s nothing in the restriction which tells me this. I only know it because I created the search folder and I know what the search folder is supposed to be looking for.

Now – what happens when we go to a different profile looking at the same mailbox? The search folder itself doesn’t get synched back to the server. Only this search folder definition message gets synced. When Outlook encounters this message, it places a dummy node under Search Folders and waits for the user to click on it. If the user does click on it, it builds the search folder using the information from PR_WB_SF_DEFINITION.

And that’s where the problem comes in: Named property mappings are store specific. A named property mapping that’s valid for one store will most likely be invalid for another store. When we switched to cached mode, we’re now working with the OST, not Exchange. Even if there happens to be an 0x8010101F property in this store, it’s not going to be the “Keywords” property. So when we clicked on the folder in Outlook, it created a search folder that searched for a bogus property.

What happened when we ran the macro is even more interesting: Outlook looked at our search string and built a search folder definition message with the appropriate PR_WB_SF_DEFINITION. However, it couldn’t save the message because it already existed, with a different definition!

Why don’t we have a problem when we create search folders directly in Outlook? The Outlook user interface severely restricts the kinds of restrictions you can set up. Because of that, Outlook is able to store the PR_WB_SF_DEFINITION in a different format (using SFST_FILTERSTREAM instead of SFST_MRES). That format is more flexible with named properties, but isn’t capable of storing the wide variety of restrictions that could be specified through the Outlook Object Model.

Workaround

We did look at taking a fix for this, but the limitations of SFST_FILTERSTREAM eliminated it as a possibility for a fix. And the fact that this problem only happens if the restriction contains a named property (IE it won’t repro on a search for subject or recipients) limited the scope of the problem.

However, for the case where the search folder is being created by a macro and we’re getting the “Cannot create folder” error, we found a neat workaround. We can use Outlook’s PropertyAccessor to delete the search folder definition message so it can be recreated. Here’s a sketch of the code:

 Sub DeleteSFItem()
     Dim CommonViewsEIDBin As String
     Dim CommonViewsEIDString As String
     Dim CommonViewsFolder As Folder
     Dim ACTable As Table
     Dim oRow As Row
     Dim SFDefinitionEID As String
     Dim SFDefinitionItem As StorageItem
         
     CommonViewsEID = Session.DefaultStore.PropertyAccessor.GetProperty( _
         "https://schemas.microsoft.com/mapi/proptag/0x35E60102")
     CommonViewsEIDString = Session.DefaultStore.PropertyAccessor.BinaryToString( _
         CommonViewsEID)
     Set CommonViewsFolder = Session.GetFolderFromID(CommonViewsEIDString)
     Set ACTable = CommonViewsFolder.GetTable( _
         "[Subject] = 'MySearchFolder'", olHiddenItems)
     
     Set oRow = ACTable.GetNextRow()
     If (Not (oRow Is Nothing)) Then
         SFDefinitionEID = oRow("EntryID")
         Set SFDefinitionItem = Session.GetItemFromID(SFDefinitionEID)
         SFDefinitionItem.Delete
     End If
 End Sub

For further reading on the inner workings of search folder definition messages, check out the Exchange Protocol Doc [MS-OSOSRCH].