Whenever someone shows me a Calendar folder where OWA and Outlook have different ideas of when recurring meetings are, I always ask if they've tried to poof the calendar. This always generates a chuckle, even though I was dead serious.
OWA doesn't know anything about expanding recurring meetings and appointments. Instead, it relies on a task which runs in Exchange that expands the recurring items for it. Under certain scenarios, Exchange may not realize that a recurring item has changed and needs expansion again. Sometimes this is because of a bug, sometimes it's because of corrupted data. Sometimes a little of both.
Anyway, the process for fixing this is called Poof. I think the name stems from the expression "Poof! Be gone!". When a Poof is performed on a calendar, Exchange deletes all of the cached expansions and performs them again.
Poof is enabled by first setting the following registry value on the Exchange server
Key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EXCDO\Parameters Value (DWORD): CalendarRecovery Data: 1
Next, in the description field of the properties of the Calendar folder, set the following text:
CleanupExpansionCachesInTheCalendarFolder
This signals Exchange to perform the Poof for this Calendar.
I haven't posted a MAPI sample in a while, so I whipped one together to demonstrate how one could Poof a set of Calendars:
http://stephengriffin.members.winisp.net/PowerPoof/PowerPoof.zip
As usual, all caveats and disclaimers for samples apply. One big caveat here - Poof is an expensive process. Running Poof against every mailbox in a server in a short period of time could place a heavy load on the server. So - if this is something you want to do, I'd recommend doing the Poof in batches, like so (assume legdns.txt contains a list of legacy DNs for mailboxes you wish to Poof):
for /F "delims=" %i in (legdns.txt) do @PowerPoof -p "Default Outlook Profile" -m "%i" -v
Note also that if you put this in a batch file, you'd need to use %%i instead of %i.
Update:
One of my colleagues pointed out why Poof sometimes won't work - if you're using a cached mode profile, when you write the text to the Calendar folder, nothing is changed on the server until the client syncs with the server. And even then it's unclear whether that would actually trigger the Poof. So - if you are performing Poof manually, make sure the Outlook profile you use isn't cached.
If you're using PowerPoof with the -m or -s switches, the connection made to the mailbox is uncached regardless of the profile type, so this won't be an issue. If you're using PowerPoof with -p and no other parameters, then it will make the setting on the calendar folder of whatever profile you specified, so if that profile is cached it's likely the Poof won't happen.
Also - another issue I've seen frequently with PowerPoof. If you run it with the legdns.txt file, make sure the DNs listed in legdns.txt do not have extra spaces at the beginning or ending of the line. If they do, those spaces will passed in with the -m switch and PowerPoof won't be able to find the mailbox.
I don't normally write much about the Outlook Object Model, but this was just too interesting to pass up. Suppose we run the following code in Outlook 2003 VBA:
Sub TestHRESULT() Dim ons As Outlook.NameSpace ' If running this code in Outlook 2007 VBA, use Outlook.Folder here Dim f As Outlook.MAPIFolder Dim imi As Outlook.MailItem Set ons = Me.Application.GetNamespace("MAPI") Set f = ons.GetDefaultFolder(olFolderInbox) Set imi = f.Items.GetFirst On Error Resume Next imi.SaveAs "c:?", Outlook.OlSaveAsType.olMSG Debug.Print "Error: " & Hex(Err.Number) End Sub
What would we expect to happen? The path we passed to SaveAs is invalid, so we should get an error. Let's see what it prints the first few times we run it:
Error: A72300FC Error: A93300FC Error: AB4300FC
What gives? Some of the digits here are constant (like the 00FC at the end) but why is the beginning changing? Turns out this is a bug in the way the Outlook Object Model is handling error codes. Under the covers, Outlook has gotten the right error code. But while passing this error around internally, the designers of Outlook wanted a simple way to track when and where the error happened. They did this by hijacking 11 unused bits of the HRESULT - specifically bits 20-30 (AKA - 0x7FF00000). What's supposed to happen is that these bits get stripped out before they get returned to the caller. However, we missed a few cases and these extra bits sometimes get out.
However, since we know what extra bits got set, we can clear them and get the real error code. Here's how we can do this in VBA:
Debug.Print "Error: " & Hex(Err.Number And Not &H7FF00000)
Which will produce the desired result:
Error: 800300FC
Looking this error code up, we find it is STG_E_INVALIDNAME, an appropriate error for an invalid file path.
BTW - we're pretty sure we've squashed this bug in Outlook 2007
We had a customer asking how to use the InternetMessageHeaders property in EWS. They found that if they asked for the prop, they only got the names of the headers, but not the values. This is one of those areas (like attachments) where EWS is only going to fetch the minimal amount of data, avoiding potentially expensive operations until you explicitly ask for them. In this case, once you have the names of the headers, you can make another call back to fetch their values. Of course, if you already knew the name of a header you wanted, then you could skip the middle step and ask for it directly.
I've updated my earlier sample to illustrate the technique.
using System; using EWS; using System.Net; namespace GetProps { class Program { static void Main(string[] args) { ExchangeServiceBinding exchangeServer = new ExchangeServiceBinding(); ICredentials creds = new NetworkCredential("SomeUser", "SomePassword", "SomeDomain"); exchangeServer.Credentials = creds; exchangeServer.Url = @"http://MyServer/EWS/Exchange.asmx"; DistinguishedFolderIdType[] folderIDArray = new DistinguishedFolderIdType[1]; folderIDArray[0] = new DistinguishedFolderIdType(); folderIDArray[0].Id = DistinguishedFolderIdNameType.inbox; PathToUnindexedFieldType ptuftDisplayName = new PathToUnindexedFieldType(); ptuftDisplayName.FieldURI = UnindexedFieldURIType.folderDisplayName; PathToExtendedFieldType pteftComment = new PathToExtendedFieldType(); pteftComment.PropertyTag = "0x3004"; // PR_COMMENT pteftComment.PropertyType = MapiPropertyTypeType.String; GetFolderType myfoldertype = new GetFolderType(); myfoldertype.FolderIds = folderIDArray; myfoldertype.FolderShape = new FolderResponseShapeType(); myfoldertype.FolderShape.BaseShape = DefaultShapeNamesType.IdOnly; myfoldertype.FolderShape.AdditionalProperties = new BasePathToElementType[2]; myfoldertype.FolderShape.AdditionalProperties[0] = ptuftDisplayName; myfoldertype.FolderShape.AdditionalProperties[1] = pteftComment; Console.WriteLine("Getting inbox"); GetFolderResponseType myFolder = exchangeServer.GetFolder(myfoldertype); FolderInfoResponseMessageType firmtInbox = (FolderInfoResponseMessageType) myFolder.ResponseMessages.Items[0]; Console.WriteLine("got folder: {0}",firmtInbox.Folders[0].DisplayName); if (null != firmtInbox.Folders[0].ExtendedProperty) { Console.WriteLine("Comment: {0}",firmtInbox.Folders[0].ExtendedProperty[0].Item.ToString()); } else { Console.WriteLine("Comment: not found"); } PathToUnindexedFieldType ptuftSubject = new PathToUnindexedFieldType(); ptuftSubject.FieldURI = UnindexedFieldURIType.itemSubject; PathToExtendedFieldType pteftFlagStatus = new PathToExtendedFieldType(); pteftFlagStatus.PropertyTag = "0x1090"; // PR_FLAG_STATUS pteftFlagStatus.PropertyType = MapiPropertyTypeType.Integer; FindItemType findItemRequest = new FindItemType(); findItemRequest.Traversal = ItemQueryTraversalType.Shallow; findItemRequest.ItemShape = new ItemResponseShapeType(); findItemRequest.ItemShape.BaseShape = DefaultShapeNamesType.IdOnly; findItemRequest.ItemShape.AdditionalProperties = new BasePathToElementType[2]; findItemRequest.ItemShape.AdditionalProperties[0] = ptuftSubject; findItemRequest.ItemShape.AdditionalProperties[1] = pteftFlagStatus; findItemRequest.ParentFolderIds = new FolderIdType[] { firmtInbox.Folders[0].FolderId }; FindItemResponseType firt = exchangeServer.FindItem(findItemRequest); foreach (FindItemResponseMessageType firmtMessage in firt.ResponseMessages.Items) { if (null != firmtMessage.RootFolder && firmtMessage.RootFolder.TotalItemsInView > 0) { foreach (ItemType it in ((ArrayOfRealItemsType)firmtMessage.RootFolder.Item).Items) { Console.WriteLine("got item: {0}",it.Subject); if (null != it.ExtendedProperty) { Console.WriteLine("Prop PR_FLAG_STATUS: {0}",it.ExtendedProperty[0].Item.ToString()); } else { Console.WriteLine("Prop PR_FLAG_STATUS: not found"); } PathToUnindexedFieldType ptuftHeaders = new PathToUnindexedFieldType(); ptuftHeaders.FieldURI = UnindexedFieldURIType.itemInternetMessageHeaders; PathToExtendedFieldType ptuftHeadersProp = new PathToExtendedFieldType(); ptuftHeadersProp.PropertyTag = "0x007D"; // PR_TRANSPORT_MESSAGE_HEADERS ptuftHeadersProp.PropertyType = MapiPropertyTypeType.String; GetItemType getItemRequest = new GetItemType(); getItemRequest.ItemIds = new ItemIdType[] { it.ItemId }; getItemRequest.ItemShape = new ItemResponseShapeType(); getItemRequest.ItemShape.BaseShape = DefaultShapeNamesType.IdOnly; getItemRequest.ItemShape.AdditionalProperties = new BasePathToElementType[2]; getItemRequest.ItemShape.AdditionalProperties[0] = ptuftHeaders; getItemRequest.ItemShape.AdditionalProperties[1] = ptuftHeadersProp; GetItemResponseType girt = exchangeServer.GetItem(getItemRequest); foreach (ItemInfoResponseMessageType grmtMessage in girt.ResponseMessages.Items) { ItemType item = grmtMessage.Items.Items[0]; if (null != item.ExtendedProperty) { Console.WriteLine("Prop PR_TRANSPORT_MESSAGE_HEADERS:\n {0}", item.ExtendedProperty[0].Item.ToString()); } else { Console.WriteLine("Prop PR_TRANSPORT_MESSAGE_HEADERS: not found"); } Console.WriteLine(); if (null != item.InternetMessageHeaders) { PathToIndexedFieldType[] headerProps = new PathToIndexedFieldType[item.InternetMessageHeaders.Length]; int index = 0; foreach (InternetHeaderType iht in item.InternetMessageHeaders) { PathToIndexedFieldType headerProp = new PathToIndexedFieldType(); headerProp.FieldURI = DictionaryURIType.itemInternetMessageHeader; headerProp.FieldIndex = iht.HeaderName; headerProps[index++] = headerProp; } GetItemType getItemRequest2 = new GetItemType(); getItemRequest2.ItemIds = new ItemIdType[] { it.ItemId }; getItemRequest2.ItemShape = new ItemResponseShapeType(); getItemRequest2.ItemShape.BaseShape = DefaultShapeNamesType.IdOnly; getItemRequest2.ItemShape.AdditionalProperties = headerProps; GetItemResponseType girt2 = exchangeServer.GetItem(getItemRequest2); foreach (ItemInfoResponseMessageType grmtMessage2 in girt2.ResponseMessages.Items) { ItemType item2 = grmtMessage2.Items.Items[0]; if (null != item2.InternetMessageHeaders) { Console.Write("Parsing internet headers"); foreach (InternetHeaderType iht2 in item2.InternetMessageHeaders) { if (null != iht2.HeaderName) { Console.Write("Header {0}", iht2.HeaderName.ToString()); if (null != iht2.Value) { Console.WriteLine(" = {0}", iht2.Value.ToString()); } else Console.WriteLine(" is null"); } } } } } } Console.WriteLine(); } } } Console.WriteLine("\nHit any key to continue"); Console.ReadKey(true); } } }