Outlook 2003 Integration API Wrapped PST Docs and Sample


[This is now documented here: http://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: http://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

Comments (46)

  1. Henry Gusakovsky says:

    Wonderfull. At last that is docummented.

    I did almost the same for PST provider from Outlook 2000 and Outlook XP.

    And my MAPISpy uses the same approach.

    I will modify a bit my code that works with previous providers according to provided info. Maybe that will help to find out any issues.

  2. s says:

    how does this integrate with the outlook UI when you click send/receive?

  3. Stephen Griffin says:

    The send and receive button doesn’t show when you’re working in the Wrapped PST. However, this sample is implemented as an Exchange Client Extension as well, so it puts its own button on the toolbar that you can use to kick off replication.

  4. s says:

    Can you expand on what an NST store is exactly? Is it specificall designed for building store providers to leverage what the pst provider does? Is there any harm in using an NST store if you are never going to use the replication api?

    From an outsiders perspective, there’s enough documentation to make this stuff work. However, to build solid products using this stuff, we could use more information. Thanks!

  5. Stephen Griffin says:

    An NST store is just a PST store that is aware that it is being wrapped. That keeps it from doing stuff that might not be safe if wrapping were not involved. Functionally, there’s not much difference, except the replication API is enabled. In many respects, the OST used in cached mode is specific instance of an NST store.

    There’s no requirement for using the replication API. As you’ll notice in the sample, the portion of the code that drives the replication isn’t even part of the wrapping.

  6. s says:

    Thanks Stephen, you’re a rock star! This documentation is extremely helpful and tremendously appreciated. Us MAPI devs would certainly love to see these and many more details included in the next doc update to the integration api’s.

  7. sample code url not working says:

    sample code url not working

  8. Ani says:

    When implementing the logon method what does this state ment mean in first bullet ‘Pass in the profile section pointed to by PR_SERVICE_UID on the default profile section’

    Can you throw some light on it?

  9. Stephen Griffin says:

    Ani – I clarified that bullet. Also, take a look at the sample code.

  10. s says:

    are you able to create a contact with the sample app? Im not. Outlook says that "The messaging interface has returned an unknown error." I can, however, save a draft. Im trying to test if contab will work propely.

    Any ideas?

    Also, how does this integrate with outlooks UI for new mail notification (the popup & tray icons)?

    Thanks again!

  11. Uday says:

    Hi Stephen,

    I got wrapping Message Store object but what if I want to wrap the folder and message objects?

  12. Stephen Griffin says:

    Uday – wrapping the folder and message objects is a very good idea. I don’t do it in the sample just because it wasn’t required to illustrate how this all works. But in a real implementation, you certainly would wrap these objects. Just make sure you catch all the OpenEntry entry points to make sure all objects that get handed out are wrapped appropriately.

  13. Andrew says:

    I not only can’t create contacts, I can’t create appointments, or sticky notes, or anything but drafts. The "New" works fine, of course, but when I hit "Save" I get the same error as "s" above. (Except that with contact dist lists, it appears to succeed, but I end up with a completely empty IPM.Note.)

  14. Stephen Griffin says:

    Andrew and s,

    Sorry for the delay. I didn’t get a chance to sit down with this until today. The problem was with the status row the PST provider set. It caused QueryIdentities to sometimes fail, which in turn caused the special item creation to fail. Interestingly, in my tests, the problem only ever happened the first time the profile was opened. Subsequent attempts to use the profile never failed for me.

    Steve

  15. to_amitmohan says:

    I am trying to use wrappst message store provider just as a store provider and not for synchronization. I am not able to successfully create a profile neither on OL2002 nor on OL2k3.

    For OL 2002 I changed the code from NSTServiceEntry to PSTServiceEntry. Still it didn’t create the profile.

    On OL2k3, it asks for a .NST file. Where can i get a sample .NST file from?.

    I have few .pst files. For OL2k3 i changed the code so it asks for .pst file. After providing with .pst file, it still didn’t work for me.

    Could you please help me creating a profile. Any help would be great.  Thanks.

  16. Stephen Griffin says:

    If you’re going to wrap the PST provider, you have to use NSTServiceEntry. When you’re asked for an NST file, just provide a file name and the file will be created.

    I strongly urge that you confirm the sample works as written before making any changes. That way when you make changes and it stops working you know what you did to break it. 🙂

  17. Griff James says:

    Does the wrapped PST sample suffer from the 2 GB limit?

  18. Stephen Griffin says:

    Ansi PSTs have a 2GB limit. Unicode PSTs do not. Wrapped PST is done around the Unicode PST.

  19. Keith Young says:

    Is there any way to:

    a) detect if an OST copy has taken place

    b) disable replication on a per-item basis?

    We want to be able to use Cached Mode with our mailbox manager, which can be used to stub items in the Exchange. We want to be able to avoid stubbing prior to replication and mark an item as not to be replicated after stubbing.

  20. chuck says:

    I tried to wrap the PST’s folder object but am only having partial success. I created a new wrapper class CMAPIFolder, then put the appropriate new CMAPIFolder call for MAPI_FOLDER types in CMsgStore::OpenEntry and CMAPIFolder::OpenEntry. The wrapper gets called for a few methods, e.g. GetProps, GetContentsTable, but not for CreateMessage, DeleteMessages, etc. Any idea what could be going wrong?

  21. chuck bohling says:

    I’m having trouble getting the wrapped PST to work correctly. I added the wrapper CMAPIFolder and changed CMsgStore::OpenEntry and CMAPIFolder::OpenEntry to wrap the returned PST IMAPIFolder. Sometimes my implementation is called; sometimes not. It’s as if outlook is calling the PST directly and bypassing my code. Any ideas what might be going wrong.

  22. Kevin says:

    I ran the install.bat file and it couldn’t find wrppst32.dll and thoughts on were I can get that?

  23. Stephen Griffin says:

    You have to build the project.

  24. Christian Jensen says:

    Can anyone tell me if there is a way to do this with .NET (c#)?

    Thanks!

  25. fred says:

    Hello, I didnt understand how to load this wrappst ? Ive compiled the source and when I start outlook I can see wrappst icon. But is there a need to create a new profile with a new service ? thx.

  26. Stephen Griffin says:

    It’s a MAPI store provider, so yes, it will need to be loaded in a profile. You can load it in a new profile or add it to an existing one.

  27. MrC says:

    Can I use this to create a msgStore using a local (well on mapped drive) mdb (access database) so as to create a shared pst (openable by many uses).

    Thanks.

    MrC

  28. Stephen Griffin says:

    No – I don’t think that would work. A PST can only be accessed by one user.

  29. P@thfinder says:

    Hi! I wrote a program to deal with my messagebox (Exchange 2003 is in use). But there’s a small problem – after a some time it crashes with error 0x8004010E – MAPI_E_NOT_ENOUGH_RESOURCES. I’ve looked through my code and found no mistakes. So it looks like program uses more and more memory each time I open my mailbox. I have no idea about the reason of such a strange behavior… 🙁

    P.S. All handles are closed and I used Release() and FreeProws() methods to free objects.

    Thanks in advance.

  30. Stephen Griffin says:

    P@thfinder – I don’t see what your question has to do with the wrapped PST sample. You should take your question to the MAPI mailing list: http://peach.ease.lsoft.com/archives/mapi-l.html

  31. Param says:

    Is there a way to write a message store that will wrap/replace the default personal folder in outlook or does it work only with exchange?

  32. Param says:

    Never mind. You can use Tools->Email Accounts in outlook to set default personal store.

  33. Helper Method says:

    Hi, i have try to use your sample in OL2007 for now. When i add a new data file and select Wrapped PST, some questions:

    – By default, the Inbox, Outbox, … folders area listed. How can i custom the sample code to add some folders and hide inbox, outbox, …. folder ?

    – I’m going to map my folder to a URL but i it does not display. I have followed the Microsoft KB to enable non-default folder homepage before.

    Please take a look at Microsoft CRM for Outlook client. When add a data file for Microsoft CRM, it does not display Inbox, Outbox,…. It displays the Account, Leads,…. folder by default, and we can change the homepage URL for that folders.

    Many thanks.

  34. Helper Method says:

    Another question: I’m a new to VC++ (old in C#, VB :D), can you remove the Add-in from your sample ? I only need the Store Provider code.

    So many thanks.

  35. Chris says:

    I’m following the WrapPST sample to programmatically create wrapped PST stores in the current Outlook session. Everything is working great apart from where I am trying to block stores from being manually created when a user goes to File->New->Outlook Data File in Outlook.

    In ServiceEntry, I’m intercepting the MSG_SERVICE_CREATE and MSG_SERVICE_CONFIGURE contexts and returning an error (e.g. MAPI_E_NO_SUPPORT) if either a window handle is provided to ServiceEntry or if the SERVICE_UI_ALLOWED flag is passed through. This successfully blocks the request in Outlook 2007, but causes Outlook 2003 to crash.

    Does anybody know why this would happen?  I’ve tried returning various MAPI error codes, but they all cause Outlook to crash.

    Thanks,

    Chris

  36. What caveats should we know about pst files sizes?  I assume we are using exchange mapi and will have a 2gig limit?  Even if we can use the unicode pst, what if we wanted to sync a span of pst files? Is that possible?

    Somehow i’m thinking ‘you-get-what-you-get’.

  37. Sandy says:

    Hi , Can i use the Replication API to sync with my server which is not MS Exchange Server.Thanks

  38. Stephen Griffin says:

    Yes – that’s the point of the replication API – to make it easier to sync from whatever source you want.

  39. Sandy says:

    Hi, i just wonder how MAPI spooler know how to route a new incoming message to my custom message store in which it is not default message store?

    As far as i know, the MAPI spooler will call the tranportLogon::StartMessage function with a lpMessage pointer which is from default store. Correct me if i’m wrong.

    Thanks.

  40. Stephen Griffin says:

    You control the replication API – deliver the messages yourself and don’t bother with the spooler.

  41. Linda says:

    what are those SetTopKey and GetSourceKey function for?

  42. Sandy says:

    I developing a custom transport provider. In the case, do i need to make any changes to adapt using Replication API at my transport provider.

    Thanks.

  43. Blake says:

    calling the following in DoSync (Provider.cpp) is returning E_NOINTERFACE. Any tips on what might be happening?

    hRes = lpMDB->QueryInterface(IID_IPSTX, (void**)&pPstx);

  44. Blake says:

    Sorry, I should have mentioned that I’m getting this error is in the wrapped PST sample loaded in Outlook 2k3 SP3. I can see that the IMSgStoreVtbl doesn’t support the interface but I’m scratching my heads to why not 🙂

  45. ivo says:

    Hi,

    I’m using the wrapped pst provider with Outlook 2007 but I can’t find what I’m looking for.

    I need to know when something in outlook is changed (created, moved, deleted or just edited). Such as folders, contacts, calendars, notes etc. So I can manipulate the changed item before it is saved to the .nst file. Can I do that with wrapped pst?

  46. Peter says:

    OL 2007: Getting E_NOINTERFACE returned from hRes = lpMDB->QueryInterface(IID_IPSTX, (void**)&pPstx); Also no WrapPST visible in Account Settings.