I said I'd do it, and now I did it. Introducing the MFCMAPI project on CodePlex: http://www.codeplex.com/MFCMAPI
Please download the source and inundate me with comments, bugs, and feature requests. There's an issue tracker tab on the CodePlex site that we can use to track bugs, as well as a discussions tab for, well, um, discussions.
Note that while I'm the only developer on the project, anyone can write an add-in now. I tried to make the add-in model as simple as possible. See the wiki page for a header file, a sample add-in, and docs. Lemme know what kinds of add-ins you dream up.
I want to thank everyone for being patient while I worked out the details of getting this source published. I hope you find the source instructive.
[This is now documented here: http://msdn2.microsoft.com/en-us/library/bb820976.aspx]
We're getting near DST time again. Last time around, we had both an Outlook based tool and an Exchange based tool for rebasing appointments. The idea was the Outlook based tool could be used by end users to update their calendars, while the Exchange based tool could be used by corporate administrators to update their users' calendars en masse.
For the most part, this worked well. One problem with the Exchange tool was that it was essentially a wrapper around the Outlook tool. That is, the Exchange tool gathered parameters and passed them to the Outlook tool to do the work. The reason this was a problem was that, in addition to the overhead of starting and stopping an external process, we also had to incur the cost of a MAPI logon and logoff for each mailbox being processed. And as any MAPI developer knows, MAPI logon and logoff is expensive and slow.
So, this time around, we've split all the code that does the rebasing work into a redistributable DLL, tzmovelib.dll. Both Outlook and Exchange rewrote their tools to use this DLL. For the Outlook tool, the effect is negligible. But for the Exchange tool, this allows us to log on to MAPI just one time and then iterate through all the mailboxes which need to be processed. This should allow the Exchange tool to run significantly faster.
As an added bonus, we now have an API that third party developers can use to perform the rebasing themselves without potentially corrupting calendars by trying to work directly in MAPI. And I got the honor of writing the documentation and a sample for it. We're putting it up here on my blog for now. MSDN documentation based on this blog entry will come later.
BTW - This DLL will ship with the updated Outlook and Exchange tools when they're released. We've also produced an MSI that can be included with third party rebasing tools. If you are writing a calendar rebasing tool and would like to use the functionality this DLL provides, drop me a note through the contact link below and I'll send you the installer. Please note that for the DLL to work, it must be installed with it's own installer.
Update (9/12/07):
Both Exchange and Outlook have shipped their updated tools.
Exchange's tool can be found here: http://support.microsoft.com/kb/941018. It uses tzmovelib.dll to do the rebasing.
Outlook's tool can be found here: http://support.microsoft.com/kb/931667. It does NOT use tzmovelib.dll (this is contrary to what I said above - I misunderstood the developer). It does use the same core logic as the Exchange tool though - it's just linked directly into the binary instead of called through a DLL.
Resources
HrCreateApptRebaser
The HrCreateApptRebaser function initializes a IOlkApptRebaser object for use in rebasing appointments.
Quick Info
Syntax
HRESULT HrCreateApptRebaser( ULONG ulFlags, IMAPISession *pSession, IMsgStore *pCalendarMsgStore, IMAPIFolder *pCalendarFolder, LPCWSTR pwszUpdatePrefix, const FILETIME *pftInstallDateUTC, LONG lExpansionDepth, const TZDEFINITION *pTZTo, const TZDEFINITION *pTZMissing, MAPIERROR **ppError, IOlkApptRebaser **ppApptRebase);
ParametersulFlags [in] Bitmask of flags used to control how rebasing is performed. The following flags can be set:
REBASE_FLAG_UPDATE_ORGANIZED_MEETINGSItems in which the user is the meeting organizer will be rebased. Note that by default this will cause meeting updates to be sent out to all attendees of any meeting being rebased. Combine this flag with either REBASE_FLAG_FORCE_NO_EX_UPDATES or REBASE_FLAG_FORCE_NO_UPDATES to change the behavior of how meeting updates are handled.REBASE_FLAG_UPDATE_UNMARKEDUpdate items that aren't marked with a time zone. If this flag is specified, the pTZMissing value passed to HrCreateApptRebaser will be used as created-in time zone for all items that do not have time zone data.REBASE_FLAG_UPDATE_ONLYRECURRINGUpdate only recurring items.REBASE_FLAG_NO_UIDo not show any UI (prevents the display of any logon dialog boxes when opening the message store).REBASE_FLAG_UPDATE_MINIMIZEAPPTSDo not rebase items that will only have changes which occur in the past.REBASE_FLAG_FORCE_REBASEDo not check the organizer for rebasing decisions (enables items in which the user is the attendee to be rebased).REBASE_FLAG_FORCE_NO_EX_UPDATESOnly send updates if the user is the organizer and recipient is non-EX.REBASE_FLAG_FORCE_NO_UPDATESNever send updates.REBASE_FLAG_ONLY_CREATED_PRE_PATCHOnly rebase single instance items created before the patch was applied.REBASE_FLAG_REPORTING_MODEDo not actually rebase, just report items that would be rebased.REBASE_FLAG_SEND_RESOURCE_UPDATESSend meeting updates to resources (attendees explicitly specified on the "Resource" line of a meeting).
pSession [in] Required. Pointer to a MAPI session interface.
pCalendarMsgStore [in] Required. Pointer to a message store containing items to be rebased.
pCalendarFolder [in] Require. Pointer to a Calendar folder containing items to be rebased.
pwszUpdatePrefix [in] Optional. Pointer to a string containing the prefix to be prepended on meeting requests. May be NULL.
pftInstallDateUTC [in] Optional. Timezone patch install date. Only used if the REBASE_FLAG_ONLY_CREATED_PRE_PATCH flag is set.
lExpansionDepth [in] Expansion depth when expanding distribution lists to exclude EX recipients. Only used if the REBASE_FLAG_FORCE_NO_EX_UPDATES flag is set.
pTZTo [in] Required. Pointer to a TZDEFINITION structure describing the time zone to be rebased to.
pTZMissing [in] Required. Pointer to a TZDEFINITION structure describing the time zone to be assumed if time zone information is not stamped on an item. Must not be NULL, but only used if the REBASE_FLAG_UPDATE_UNMARKED flag is set.
ppError [out] Optional. Pointer to a pointer to a MAPIERROR structure containing version, component, and context information for the error. Can be NULL if no extended error information is desired. Free with MAPIFreeBuffer.
ppApptRebase [out] Pointer to a pointer to the returned IOlkApptRebaser interface.
Remarks
Obtain this function from tzmovelib.dll by calling GetProcAddress with the function name HrCreateApptRebaser@44.
Not all of the flags are valid in combination with each other. View the "Glossary of command-line options for the Outlook Time Zone Data Update tool" section of http://support.microsoft.com/kb/933146 for more information on the various options.
IOlkApptRebaser : IUnknown
The IOlkApptRebaser interface is used to rebase appointments in a calendar folder.
Vtable Order
IOlkApptRebaser::BeginEnumerateAppointments
The IOlkApptRebaser::BeginEnumerateAppointments method begins a task for appointment enumeration. This task runs on a new thread.
HRESULT BeginEnumerateAppointments( PFNREBASETASKPROGRESS pfnProgress, void **ppContext);
Parameters
pfnProgress [in] Optional. Pointer to a rebase task progress function to receive progress.
ppContext [out] Required. Pointer to a pointer to the returned context. This context will be passed to EndEnumerateAppointments.
IOlkApptRebaser::EndEnumerateAppointments
The IOlkApptRebaser::EndEnumerateAppointments method waits for appointment enumeration to complete and retrieves the results.
HRESULT EndEnumerateAppointments( void *pContext, HRESULT *phResult, MAPIERROR **ppError, SRowSet **ppRows);
pContext [in] Required. Pointer to the context obtained from a call to BeginEnumerateAppointments.
phResult [out] Required. Pointer to an HRESULT to retrieve the result of the enumeration operation.
ppError [out] Optional. Pointer to a pointer to a MAPIERROR structure to retrieve extended error information.
ppRows [out] Required. Pointer to a pointer to an SRowSet structure describing the appointments which need rebasing. This structure will be passed to BeginRebaseAppointments.
IOlkApptRebaser::BeginRebaseAppointments
The IOlkApptRebaser::BeginRebaseAppointments method begins a task for appointment rebasing. This task runs on a new thread.
HRESULT BeginRebaseAppointments( const SRowSet *pRows, PFNREBASETASKPROGRESS pfnProgress, PFNREBASETASKCOMPLETE pfnComplete, void **ppContext);
pRows [in] Required. Pointer to an SRowSet structure describing the appointments which need rebasing. This structure can be obtained from EndEnumerateAppointments.
pfnComplete [out] Optional. Pointer to a rebase task completion function to receive notification of rebase completion.
ppContext [out] Required. Pointer to a pointer to the returned context. This context will be passed to EndRebaseAppointments.
IOlkApptRebaser::EndRebaseAppointments
The IOlkApptRebaser::EndRebaseAppointments method waits for appointment rebasing to complete and retrieves the results.
HRESULT EndRebaseAppointments( void *pContext, HRESULT *phResult);
pContext [in] Required. Pointer to the context obtained from a call to BeginRebaseAppointments.
phResult [out] Required. Pointer to an HRESULT to retrieve the result of the rebasing operation.
RebaseTaskProgress
The RebaseTaskProgress function reports progress for enumeration and rebasing of appointments. Clients using the IOlkApptRebaser interface implement this function to track item processing.
void STDAPICALLTYPE RebaseTaskProgress( ULONG ulMin, ULONG ulMax, ULONG ulCur, REBASE_APPT_STATE State, const SRow* pRowCur);
ParametersulMin [in] The low end of the range of appointments being processed. Will usually be zero.
ulMax [in] The high end of the range of appointments being processed. Will usually be the number of items in the calendar folder being processed.
ulCur [in] The current item being processed.
State [in] A value indicating the status of the item being processed. Will be one of the following values:
REBASE_APPT_STATE_SCANNING_EXAMINING - Scanning, examining an item.REBASE_APPT_STATE_SCANNING_FOUND - Scanning, found an item.REBASE_APPT_STATE_BEGIN - Fixing, starting an item.REBASE_APPT_STATE_REBASING - Fixing, adjusting an item.REBASE_APPT_STATE_SENDING - Fixing, sending a meeting update.REBASE_APPT_STATE_DONE - Fixing, done with item.
pRowCur [in] Pointer to an SRow structure describing the item being scanned or fixed.
RebaseTaskComplete
The RebaseTaskComplete function reports completion for rebasing of appointments. Clients using the IOlkApptRebaser interface implement this function to track completion of item updates.
void STDAPICALLTYPE RebaseTaskComplete( ULONG ulRowIndex, const SRow* pRowCur, HRESULT hrResult, BOOL fModified, BOOL fSentUpdate, const MAPIERROR* pError);
ParametersulRowIndex [in] The row which was processed. This index refers to the SRowSet structure passed to BeginRebaseAppointments.
pRowCur [in] Pointer to an SRow structure describing the item which was processed.
hrResult [in] An HRESULT indicating the result of the rebasing operation.
fModified [in] A boolean indicating whether the item was modified.
fSentUpdate [in] A boolean indicating whether a meeting update message was sent.
pError [in] Pointer to a MAPIERROR structure with extended error information.
Update: 8-20-07 - Reworded section about redistributable package. Added remark about combining flags.
Update: 11-02-07 - The redistributable package will be available soon on the MSDN, so don't contact me about obtaining it. Instead, consult the link at the top of this article.
It's just a day of cool fixes. For months now, people have been bugging me about the fact that MAPIFindNext doesn't work in Vista. I knew we had a fix in the works, but I wasn't able to give any more details than that.
Well, the wait is over - this issue has been fixed: http://support.microsoft.com/kb/939718.
Those who look closely at the fix will notice that the file getting the fix is rpcrt4.dll, not in MAPI. What had happened was portions of the code for marshalling parameters across process boundaries was rewritten in Vista. In doing this rewriting, a very obscure corner case was handled improperly, causing the marshalling to fail. The Windows Mail implementation of MAPIFindNext just happened to reliably hit that corner case.
The fix of course was to make sure that case was handled, and now Simple MAPI works in Vista. Enjoy!
I know a number of you have asked about this - the latest CDO downloads didn't work with older versions of Exchange because they're passing a flag that allows them to work with Exchange 2007. I alluded to this problem (without actually calling it out) in my post about CONNECT_IGNORE_NO_PF.
Meanwhile, Jeff has been working on getting this fixed. It's done now. Jeff has a great post on the fix he pushed through for the CDO downloads to allow them to work with older Exchange servers and still be able to deal with Public Folder-less Exchange 2007 servers.
So here's my take on this. Only set the "Ignore No PF" flag to 1 if you know you're dealing with just Exchange 2007, or if you can somehow guarantee that any Exchange 2003 servers have been patched. Otherwise leave it alone.
If you're in a mixed server environment (probably the case for most shops running Exchange 2007 right now) then you have to have Public Folders, so not setting the flag is the right thing to do.
I haven't tested it yet, but I believe the get-PublicFolderDatabase cmdlet can be used to determine if you have any public folders in an Exchange 2007 environment.