Let’s have fun with the object model and search folders:
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
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:
And we can use MFCMAPI’s Smart View feature to parse the search folder definition stored in PR_WB_SF_DEFINITION:
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( _
"http://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
For further reading on the inner workings of search folder definition messages, check out the Exchange Protocol Doc [MS-OSOSRCH].
We had an issue recently where DDE broadcasts were being blocked on a system. The customer noticed that the problem happened after they called MAPIInitialize on their worker thread. Our investigation gives us a chance to add a 5th rule to the MAPI Multithreading Rules I wrote about a few years ago:
5. The first thread to initialize MAPI must pump messages.
To understand this, first we need to know a bit about DDE. Turns out, Raymond wrote about it a couple times already, so I don’t need to rehash that. The second thing to know is that MAPI uses a hidden window for idle processing. If you dig around a bit, you might even find the window, named “WMS Idle”. MAPI only needs one of these per process, so it’s created on the first thread to initialize MAPI. And as we know, any thread with a window on it must pump messages.
In the customer’s case, they were pumping on the main thread, but not on the MAPI worker thread. Once they inserted message pumping into the MAPI worker thread, DDE was no longer blocked. Alternatively, they could have called HrDispatchNotifications, which is a simple way to pump messages, or called MAPIInitialize on the main thread.
The May 2009 Release (build 6.0.0.1013) is live: http://mfcmapi.codeplex.com.
The coolest new feature has to be the addition of Smart View parsing to the Hex Editor. I actually had the idea to do this the morning I published the March 2009 release, but didn’t want to delay that release any longer. One side effect of exposing the parsing in the Hex Editor is I was able to do much better testing of the existing parsing engine and fix a bunch of memory leaks and crashes.
Here's a change list - see the Issue Tracker on Codeplex for more details, or look at the code:
Enjoy.
Just wanted to note that the April Cumulative Update for Outlook 2007 fixes the unaligned data issue with the PST provider. So passing a statically allocated NULL string as a PST password should no longer be a problem.
BTW – Outlook’s update isn’t listed in the overview article for the April Update. This is because the Outlook package was delayed for a few days. The overview article should eventually be updated to include Outlook.