Wednesday, May 31, 2006

MAPI Profile Provider

Hi,

Sergey Golovkin and I have created a new article about MAPI Profile provider.

See this link for details.

Friday, February 03, 2006

MAPI Store provider: creation, initialization,loading ...

MAPI store provider is the most difficult part of implementation of MAPI service. So lets see some initial aspects of How it works.

First just some summary:

  • 1. Creation of MAPI service.
  • 2. Configuring service.
  • 3. First loading of store provider.
  • 4. Subsequent loading of store provider.
  • 5. Opening message store objects.

Now lets look at these steps more closely.

1. Creation of MAPI service. When user adds MAPI service to profile MsgServiceAdmin::CreateMsgService is being called. It uses the information of message service from MAPISVC.INF file. MAPI subsystem extracts all information about service and creates profile sections for every provider in the service and profile section for the service itself. These profile section are filled with properties from correspondent sections of MAPISVC.INF file.

Snippet from MAPISVC.INF for Microsoft Personal folders provider:

[Services]
MSPST MS=Personal Folders File (.pst)

;//That is service section
[MSPST MS]
;//That variable contains list of providers in the service
;//order is the following (important)
;//Address Book providers
;//Message Store providers
;//Transport providers
;//Hook providers
Providers=MSPST MSP
PR_SERVICE_DLL_NAME=mspst.dll
PR_SERVICE_INSTALL_ID={6485D262-C2AC-11D1-AD3E-10A0C911C9C0}
PR_SERVICE_SUPPORT_FILES=mspst.dll
PR_SERVICE_ENTRY_NAME=PSTServiceEntry
PR_RESOURCE_FLAGS=SERVICE_NO_PRIMARY_IDENTITY

;//That is provider section
[MSPST MSP]
;//PR_MDB_PROVIDER property
34140102=4e495441f9bfb80100aa0037d96e0000
PR_PROVIDER_DLL_NAME=mspst.dll
PR_SERVICE_INSTALL_ID={6485D262-C2AC-11D1-AD3E-10A0C911C9C0}
PR_RESOURCE_TYPE=MAPI_STORE_PROVIDER
PR_RESOURCE_FLAGS=STATUS_DEFAULT_STORE
PR_DISPLAY_NAME=Personal Folders
PR_PROVIDER_DISPLAY=Personal Folders File (.pst)

Some providers like Microsoft Exchange contains additional profile sections described in MAPISVC.INI.

[MSEMS]
PR_DISPLAY_NAME=Microsoft Exchange Server
;//That variable contains list of additional profile sections
Sections=MSEMS_MSMail_Section
PR_SERVICE_DLL_NAME=emsui.dll
PR_SERVICE_INSTALL_ID={6485D26A-C2AC-11D1-AD3E-10A0C911C9C0}
PR_SERVICE_ENTRY_NAME=EMSCfg
PR_RESOURCE_FLAGS=SERVICE_SINGLE_COPY
;WIZARD_ENTRY_NAME=EMSWizardEntry
Providers=EMS_DSA, EMS_MDB_public, EMS_MDB_private, EMS_RXP, EMS_MSX, EMS_Hook
PR_SERVICE_SUPPORT_FILES=emsui.dll,emsabp.dll,emsmdb.dll

[MSEMS_MSMail_Section]
;//That variable defines MAPIUID value for that section
UID=13DBB0C8AA05101A9BB000AA002FC45A
66000003=01050000
66010003=04000000
66050003=03000000
66040003=02000000

Usualy that additional (shared) sections are being used for clients and providers. Provider sections are restricted by MAPI subsystem to be used only from inside provider context(via IMAPISupport object). Clients also can get access to that sections but they need to use some hacks to do that. So shared section is a good solution for that. (BTW: clients – code that uses IMAPISession context) As soon as sections are created and filled MAPI calls service configuration routine with context MSG_SERVICE_CREATE. Not all providers implement this context actualy MAPI subsystem ignores errors (not all may be) returned from within that context. F.e. MSPST provider usualy return MAPI_E_NOT_FOUND in this context.

2. Configuring service.  More interesting context is MSG_SERVICE_CONFIGURE. This context is while calling IMsgServiceAdmin::ConfigureMsgService. Some magic happens here :). The most important part of that context is creating Message Store EntryID. (if it is not created before). Provider obtains necessary info from profile, passed properties (if they exist) or asks user to enter additional data (if that is allowed by flags). Having all info provider can create EntryID for the store. Format of EntryID is not strictly defined and is up to provider provider writers. MAPI treats this EntryID like black box. Although provider is aware of that EntryID and information that it contains. Especialy that is the case when provider supports multiple different stores in the same profile like MSPST does. Using info from entryid provider should distinct one store from another. I’ll say some words about that later. So EntryID is created and we need to write it to the profile section. Before saving provider uses WrapStoreEntryID function (IMAPISupport::WrapStoreEntryID is not supported in the service context) to create wrapped EntryID. Provider stores wrapped store EntryID to the profile section. That is very important part. MAPI uses szDLLName parameter of WrapStoreEntryID later while loading provider. So if this information is wrong provider cannot be loaded afterwards.

3. First loading of store provider. To open message store we need to retrive it’s EntryID from Message Store table(IMAPISession::GetMsgStoresTable). This table contains Wrapped EntryID of all message store providers of the profile. So when clients call IMAPISession::OpenMsgStore passing Store EntryID MAPI obtains provider dll name from wrapped store EntryID. After that it tries loading dll and obtains address of MSProviderInit function. If it is succeeded it calls that function and obtains IMSProvider instance. Now MAPI should find out what profile section should it assing for that provider instance (Any way it knows that but it tries to ensure :)). It obtains EntryID from profile section unwrapps it and calls IMSProvider::CompareStoreIDs if call is succeeded and result is TRUE MAPI constructs support object assigns this section as default section for the provider and calls IMSProvider::Logon passing unwrapped EntryID and constructed support object. It none of EntryIDs succeeded MAPI constructs suport object and assings temporary profile section as a default section for this instance of provider. That temp section contains only 2 properties PR_RESOURCE_FLAGS and PR_ENTRYID. PR_RESOURCE_FLAGS property contains STATUS_TEMP_SECTION flag. PR_ENTRYID property contains EntryID that client passed to IMAPISession::OpenMsgStore. Now it’s time for provider to logon to the selected message store. It uses information from profile section and if it is allowed it can ask additional information from user (something like passwords and so on). If logon is succeeded provider needs to make some MAPI calls:

  • 1. It needs to register at least one UID (IMAPISupport::SetProviderUID) to allow MAPI subsystem opening store objects. As a rule that is PR_STORE_RECORD_KEY. That uid is MAPIUID from EntryID of all objects. EntryID of all store object contains flags first 4 bytes after that UID follows. So MAPI uses that UID to select correspondent provider instance to open objects via IMAPISession::OpenEntry.
  • 2. It needs to call IMAPISupport::ModifyProfile and register it default profile section as it’s resource. You don’t need to call it if MDB_TEMPORARY flag is passed to IMSProvider::Logon or if default section is also temporary.
  • 3. It needs to call IMAPISupport::ModifyStatusRow to update MAPI status table.
  • 4. Construct IMsgStore object and IMSLogon object and increment reference to the support object (IMAPISupport::AddRef).

After that provider registers IMsgStore object in the list of already logged stores using information from passed EntryID. That is for subsequent logon calls. Finally provider returns IMsgStore and IMSLogon objects to MAPI subsystem.

4. Subsequent loading of store provider. When client calls IMAPISession::OpenMsgStore second time for the same IMAPISession MAPI calls IMSProvider::Logon again but provider should check if passed EntryID references to already logged on store and return IMsgStore object of existing store (Shared store). If it is the case IMSLogon object is not created this time and NULL is returned and references to IMAPISupport object is not incremented.

5. Opening message store objects. When client calls IMAPISession::OpenEntry MAPI extracts UID from passed EntryID and tries to determine provider that’ve registered that UID. If no provider is found MAPI_E_UNKNOWN_ENTRYID is returned to the client. If provider is found MAPI calls its IMsgStore::OpenEntry function passing EntryID and other arguments there. So if provider uses unregistered UID in some EntryIDs that objects cannot be opened via IMAPISession::OpenEntry but can be opened via IMsgStore::OpenEntry if clients know in what store this object reside.

What else should we know. MAPI can have several instances of the same provider. I mean that MAPI can call MSProviderInit several times. MAPI calls MSProviderInit once per context for every provider. IMSProvider instance contains context of the provider. For every new IMAPISession MAPI loads separate instance of IMSProvider. Also additional instanse is loaded for spooler context. So at the same time there can be several instances of the same provider using the same store.

Last updated 03.02.2006

MSLMS small updates for compiling under MS Visual Studio 2005

See updated project there