Outlook 2003 Integration API Wrapped PST Docs and Sample

[This is now documented here: https://msdn.microsoft.com/en-us/library/bb821132.aspx]

[This sample is now part of the Outlook MAPI Code Samples!]

In the Outlook 2003 Integration API, we documented the Replication API. This is a nifty API for implementing replication between a wrapped PST and your own custom back end, which should be a great way to simplify implementation of a MAPI Message Store Provider without incurring all the pain involved in writing one from scratch. Unfortunately, we neglected to document how to create a wrapped PST. This documentation (which should eventually be part of the MSDN) corrects this oversight. Much thanks to Fergus Wilson of Meridio for his assistance in testing this sample.

Please post back any problems you encounter with this sample or the docs so I can correct them. Thanks!

The sample code can be downloaded here: https://stephengriffin.members.winisp.net/wrappst/wrappst.zip

Wrapping the PST provider in preparation for using the Replication API
The Replication API can only be used in conjunction with a Wrapped PST provider. This document defines a Wrapped PST provider and gives instructions for constructing one.

Wrapped PST Provider:
A wrapped PST provider is a custom MAPI message store provider which uses the PST provider as the back end for storing data. Most functions in a basic wrapped PST provider will proxy their arguments directly to the underlying PST provider. This allows the developer doing the wrapping to focus on the portions of the provider specific to their business needs without needing to invest time in writing a store provider from scratch.

Some functions and classes do need special logic in them to inform the PST provider that it has been wrapped so the Replication API can function. This document outlines what needs to be done in these functions.

The remaining code in a wrapped PST can be used to change default behavior, such as supporting additional properties not native to a PST by overriding GetProps, or allowing OpenEntry to do special processing on a message before handing it back to the client.

Constants:
The following constants are used in this documentation. Note that some changes and clarifications have been made to the constant definitions given in the Replication API.

 #define MDB_OST_LOGON_UNICODE ((ULONG) 0x00000800)
#define MDB_OST_LOGON_ANSI    ((ULONG) 0x00001000)

// OffLineFileInfo
typedef struct {
    ULONG    ulVersion;     // Structure version
    MAPIUID  muidReserved;
  ULONG    ulReserved;
    DWORD    dwAlloc;       // Number of primary source keys
    DWORD    dwReserved;
    LTID     ltidReserved1;
 LTID     ltidReserved2;
} OLFI;

#define PR_OST_OLFI PROP_TAG(PT_BINARY, 0x7C07)
#define OLFI_VERSION 0x01

// UID of an NST provider
const MAPIUID g_muidProvPrvNST =
 { 0xE9, 0x2F, 0xEB, 0x75, 0x96, 0x50, 0x44, 0x86,
     0x83, 0xB8, 0x7D, 0xE5, 0x22, 0xAA, 0x49, 0x48 };

// This is a clarification of the definitions of PXIHC and PXICC 
// mentioned in the Replication API
DECLARE_MAPI_INTERFACE_PTR(IExchangeImportHierarchyChanges,PXIHC);
DECLARE_MAPI_INTERFACE_PTR(IExchangeImportContentsChanges,PXICC);

// It is essential that the FEID, MEID and SKEY structures
// be wrapped in this pack pragma or they will be the wrong size
#pragma pack(1)

struct FEID
{
 BYTE    abFlags[4];
 MAPIUID muid;
   WORD    placeholder;
    LTID    ltid;
};

struct MEID
{
 BYTE    abFlags[4];
 MAPIUID muid;
   WORD    placeholder;
    LTID    ltidFld;
    LTID    ltidMsg;
};

struct SKEY
{
  GUID    guid;
   BYTE    globcnt[6];
};

#pragma pack()

MSProviderInit
This is the entry point when your provider is being loaded by Outlook. Any message store provider needs to implement this function and expose it as an entry point in the store provider’s DLL. This is a well known function name which does not change.

Implementation:

  • Save off the MAPI memory management routines and be sure to use them when managing MAPI memory.
  • Use GetProcAddress to load the “MSProviderInit” function out of MSPST32.dll.
  • Call the PST’s MSProviderInit with all the same parameters that were passed in, with the exception of the HINSTANCE parameter. This needs to be the handle of MSPST32.dll as returned by LoadLibrary.
  • Wrap the returned LPMSPROVIDER in your own implementation and return it. See CMSProvider.

ServiceEntry
This is the entry point when your provider is being configured, such as through the Mail Control Panel. The prototype for this function is MSGSERVICEENTRY, but the name to be used is not mandated. Instead, it is advertised in the PR_SERVICE_ENTRY_NAME property of your profile. In this documentation, we use the name ServiceEntry. Be sure to expose the function as an entry point in the store provider’s DLL.

Implementation:

  • Call GetMemAllocRoutines off of the passed in LPMAPISUP to get your MAPI memory management routines and be sure to use them when managing memory.
  • The following is for handling MSG_SERVICE_CREATE and MSG_SERVICE_CONFIGURE. Other contexts will require different actions according to the documentation. In particular, MSG_SERVICE_DELETE should be used to perform appropriate cleanup. Contexts which are not implemented can return MAPI_E_NO_SUPPORT.
  • Open the default profile section in which to set our properties using IProviderAdmin::OpenProfileSection, passing NULL for the LMMAPIUID.
  • If cValues and lpProps are both non-null, use SetProps to write them to the profile section.
  • Ensure PR_PROFILE_OFFLINE_STORE_PATH is set in this profile section to the path to your NST file.
  • Ensure PR_MDB_PROVIDER is set in this profile section to g_muidProvPrvNST
  • Wrap the passed in LPMAPISUP in your own implementation. Pass the profile section over to this object. See CSupport.
  • Use GetProcAddress to load the “NSTServiceEntry” function out of MSPST32.dll.
  • Call NSTServiceEntry as follows:
    • Pass the handle to MSPST32.dll in for the HINSTANCE
    • Pass the wrapped support object for LPMAPISUP
    • Use GetProps to get PR_PROFILE_OFFLINE_STORE_PATH and PR_MDB_PROVIDER from the profile section and pass this in for the LPSPropValue parameter
    • Pass NULL for the LPPROVIDERADMIN
  • After calling NSTServiceEntry, finish initializing store properties in the profile. This means
    • Create an EntryID using g_muidProvPrvNST and the path to the store:
      • The size of the entry ID should be 21 bytes plus the length of the path (including file name) plus 1 for the NULL terminator.
      • The first four bytes are NULL..
      • The next 16 are g_muidProvPrvNST.
      • The next byte is NULL.
      • The rest of the entry ID is the path to the store, including the NULL terminator.
    • Wrap it using WrapStoreEntryID with the name of the provider DLL.
    • Get PR_STORE_PROVIDERS from the default profile section and use it to open the store profile section
    • Set the following properties on both the default profile section and on the store profile section:
      • PR_ENTRYID, PR_STORE_ENTRYID: The wrapped EntryID
      • PR_RECORD_KEY: The value of PR_SERVICE_UID from the store profile section
      • PR_STORE_RECORD_KEY, PR_SEARCH_KEY, PR_PROFILE_SECURE_MAILBOX: A UID obtained from IMAPISupport::NewUID (use the same UID for all three properties)
      • PR_PROFILE_USER: The user name. The actual value doesn’t matter as much as the fact that it is set. See IMAPISupport::ModifyStatusRow.

CMSProvider
This is the wrapped implementation of IMSProvider. It is required to wrap this interface to provide some special processing needed to make the wrapped PST work.

Member functions not documented here can just pass their parameters into the underlying wrapped object. The IUnknown functions are implemented normally, making sure that the reference count on the underlying object is maintained.

The constructor should accept the LPMSPROVIDER object being wrapped.

The following functions need special handling:
IMSProvider::Logon

  • An LPMAPISUP object is passed into this function. Wrap this object in your own implementation. Request PR_SERVICE_UID from the default profile section and call OpenProfileSection. Pass the resulting LPPROFSECT into your wrapped support object for later use. See CSupport.
  • Add MDB_OST_LOGON_UNICODE to the set of flags and remove MDB_OST_LOGON_ANSI if it’s there
  • Call the underlying wrapped object’s Logon function with the passed in parameters, the wrapped support object and the modified flags.
  • Set PR_OST_OLFI on the returned LPMDB with an OLFI structure with dwAlloc set to 0x7fffffff
  • [Optional] Wrap the returned LPMDB. This is not strictly required, but this is the entry point into wrapping all objects used by the client.

IMSProvider::SpoolerLogon

  • An LPMAPISUP object is passed into this function. Wrap this object in your own implementation. Pass in the profile section pointed to by PR_SERVICE_UID on the default profile section.
  • Call the underlying wrapped object’s Logon function with the passed in parameters.

CSupport
This is the wrapped implementation of IMAPISupport. It is required to wrap this interface to provide some special processing needed to make the wrapped PST work.

Member functions not documented here can just pass their parameters into the underlying wrapped object. The IUnknown functions are implemented normally, making sure that the reference count on the underlying object is maintained.

The constructor should accept the LPMAPISUP object being wrapped and a LPPROFSECT object which will be used in OpenProfileSection.

The following functions needs special handling:
IMAPISupport::OpenProfileSection

When pbNSTGlobalProfileSectionGuid is requested, return the profile section which has been cached in your implementation.

IMAPISupport::ModifyStatusRow

The PST provider will attempt to set a status row with PR_IDENTITY_DISPLAY, PR_IDENTITY_ENTRYID, and PR_IDENTITY_SEARCH_KEY. These values will be based on the user specified in PR_PROFILE_USER and assume the user has an EX address type. If this status row is allowed, then later calls to QueryIdentity can fail if no transport can handle this address type. This failure can in turn prevent creation of some items like contacts and appointments. Simply clearing these values will fix this problem. A more advanced fix would be to set a custom status row. Even more advanced would be to wrap the IMSLogon object returned in IMSProvider::Logon to implement a custom or wrapped status entry.

Using the Replication API
The documentation of the Replication State Machine describes how to use the API to perform synchronization between an external store and the wrapped PST. See the functions DoSync, SyncDownloadFolders and SyncUploadFolders for sample code illustrating the API.

[10/6/05 - 10:39 AM] Clarified IMSProvider::Logon

[10/17/05 - 5:45PM] Added IMAPISupport::ModifyStatusRow, updated sample code

[11/11/05] Fixed sample URL