Among all well-known MAPI provider types there is Profile provider. This link contains the very short summary about it. If we dig a bit deeper into msmapi32.dll (implementation of MAPI subsystem) just using Depends utility form Visual Studio. There is exported function PRProviderInit. Msmapi32 export list

 

 

 

 

 

 

 

 

That is entry point of profile provider.

This provider is like any other provider in MAPI.

HRESULT STDMAPIINITCALLTYPE PRProviderInit(
    HINSTANCE hInstance,               // GetModuleHandle("MAPI32.DLL")
    LPMALLOC lpMalloc,                 // MAPIGetDefaultMalloc()
    LPALLOCATEBUFFER lpAllocateBuffer, // &MAPIAllocateBuffer
    LPALLOCATEMORE lpAllocateMore,     // &MAPIAllocateMore
    LPFREEBUFFER lpFreeBuffer,         // &MAPIFreeBuffer
    ULONG ulFlags,                     // 0, MAPI_USE_GIVEN_PROFILE, MAPI_NT_SERVICE, MAPI_USE_DEFAULT
    ULONG ulMAPIVer,                   // CURRENT_SPI_VERSION = 0x10010
    ULONG *lpulProviderVer,
    LPIPRPROVIDER *lppPRProvider);     // [in] HKEY if MAPI_USE_GIVEN_PROFILE set.
                                       // [out] IPRProvider

How that can be useful ?

We can write our custom provider to store profiles anywhere we want. We can store profiles in Active Directory or anoter databases such MS SQL/Oracle etc. So if user logons from another computer inside domain it can always get the same Outlook configuration. I know that it not so easy for store providers but we can just try to solve that.

Another case using exchange provider from inside NT Service (IIS). Since default provider stores it’s data in the registry the service should be started under some user account or user should be logged on before initializing MAPI subsystem. Exchange MAPI implementation and Outlook’s one are different but both using the same approach. Since Microsoft dropped support of profiles based on temporary files in Outlook 2003 we can always make another one :).

Profile Provider does:

So that is main Profile provider interface:

DEFINE_OLEGUID(IID_IPRProvider, 0x000203F6, 0, 0);

DECLARE_MAPI_INTERFACE_PTR(IPRProvider, LPIPRPROVIDER);

#define MAPI_IPRPROVIDER_METHODS(IPURE)                                 \
    MAPIMETHOD(GetLastError)                                            \
        (THIS_  HRESULT                      hResult,                   \
                ULONG                        ulFlags,                   \
                LPMAPIERROR FAR *            lppMAPIError) IPURE;       \
    MAPIMETHOD(Shutdown)                                                \
        (THIS_  ULONG FAR *                  lpulFlags) IPURE;          \
    MAPIMETHOD(OpenProfile)                                             \
        (THIS_  LPMAPISUP                    lpMAPISupport,             \
                LPSTR   FAR *                lpszProfileName,           \
                LPSTR                        lpszPassword,              \
                ULONG                        ulFlags,                   \
                ULONG_PTR                    ulUIParam,                 \
                ULONG   FAR *                lpScrambleSecurityIDSize,  \
                LPVOID  FAR *                lppScrambleSecurityID,     \
                ULONG*                       p8_var_10,                 \
                LPMAPIPROFILE FAR *          lppMAPIProfile) IPURE;     \
    MAPIMETHOD(CreateProfile)                                           \
        (THIS_  LPSTR                        lpszProfileName,           \
                LPSTR                        lpszPassword,              \
                ULONG                        ulFlags) IPURE;            \
    MAPIMETHOD(DeleteProfile)                                           \
        (THIS_  LPSTR                        lpszProfileName,           \
                ULONG                        ulFlags) IPURE;            \
    MAPIMETHOD(ChangeProfilePassword)                                   \
        (THIS_  LPSTR                        lpszProfileName,           \
                LPSTR                        lpszOldPassword,           \
                LPSTR                        lpszNewPassword,           \
                ULONG                        ulFlags) IPURE;            \
    MAPIMETHOD(GetProfileTable)                                         \
        (THIS_  ULONG                        ulFlags,                   \
                LPMAPITABLE FAR *            lppTable) IPURE;           \
    MAPIMETHOD(CopyProfile)                                             \
        (THIS_  LPSTR                        lpszOldProfileName,        \
                LPSTR                        lpszOldPassword,           \
                LPSTR                        lpszNewProfileName,        \
                ULONG_PTR                    ulUIParam,                 \
                ULONG                        ulFlags) IPURE;            \
    MAPIMETHOD(OpenProperty)                                            \
        (THIS_  ULONG                        ulPropTag,                 \
                LPCIID                       lpiid,                     \
                ULONG                        ulInterfaceOptions,        \
                ULONG                        ulFlags,                   \
                LPUNKNOWN FAR *              lppUnk) IPURE;             \
    MAPIMETHOD(SetDefaultProfile)                                       \
        (THIS_  LPSTR                        lpszProfileName,           \
                ULONG                        ulFlags) IPURE;            \
    MAPIMETHOD(ListDeferredDeletes)                                     \
        (THIS_  ULONG                        ulFlags,                   \
                LPSTR    FAR *               lppszProfilesName) IPURE;  \


#undef       INTERFACE
#define      INTERFACE  IPRProvider
DECLARE_MAPI_INTERFACE_(IPRProvider, IUnknown)
{
    BEGIN_INTERFACE
    MAPI_IUNKNOWN_METHODS(PURE)
    MAPI_IPRPROVIDER_METHODS(PURE)
};

OpenProfile method is very similar to IMSProvider::Logon on success it returns pointer to the profile interface.

It’s interface is easy enough:

DEFINE_OLEGUID(IID_IMAPIProfile, 0x000203F7, 0, 0);

DECLARE_MAPI_INTERFACE_PTR(IMAPIProfile, LPMAPIPROFILE);

#define MAPI_IMAPIPROFILE_METHODS(IPURE)                \
    MAPIMETHOD(OpenSection)                             \
        (THIS_  LPMAPIUID          lpUID,               \
                ULONG              ulFlags,             \
                LPPROFSECT FAR *   lppProfSect) IPURE;  \
    MAPIMETHOD(DeleteSection)                           \
        (THIS_    LPMAPIUID        lpUID) IPURE;        \

#undef       INTERFACE
#define      INTERFACE  IMAPIProfile
DECLARE_MAPI_INTERFACE_(IMAPIProfile, IUnknown)
{
    BEGIN_INTERFACE
    MAPI_IUNKNOWN_METHODS(PURE)
    MAPI_IMAPIPROFILE_METHODS(PURE)
};

To register custom profile provider we need to add a section to the well-known win.ini file that reside in the windows directory.
;That is registration of custom profile manager
[MAPI]
Profile DLL=D:\work\ProfileManager\debug\ProfileManager.dll

Easy enough ? Not so strainforward :). Since Outlook XP MAPI checks process name and if it is “OUTLOOK.EXE” it always use standard profile provider. So just copy “OUTLOOK.EXE” to “MSOUTLOOK.EXE” and custom provider will be loaded if it exist.

Also we can use these interface to obtain restricted profile sections. Restricted profile sections - that is provider section and by default MAPI allows access only from within provider context. If you try to open such sections via IMAPISession::OpenProfileSection or IMsgServiceAdmin::OpenProfileSection or IProviderAdmin::OpenProfileSection you will get MAPI_E_NO_ACCESS. Microsoft solved that problem since Outlook XP. There is special flag MAPI_FORCE_ACCESS to pass to OpenProfileSection. For Outlook 2000 you need to use some hacking method. See that article http://support.microsoft.com/?kbid=228736 for details. Using above interfaces you can get access to any section under all versions of Outlook & Exchange.

From: http://support.microsoft.com/?kbid=228736
File: Profiles.cpp
...
///////////////////////////////////////////////////////////////////
// HACK CENTRAL. This is a MAJOR hack. MAPI will always return E_ACCESSDENIED
// when we open a profile section on the service if we are a client. The workaround
// (HACK) is to call into one of MAPI's internal functions that bypasses
// the security check. We build a Interface to it and then point to it from our
// offset of 0x48. USE AT YOUR OWN RISK! NOT SUPPORTED!
interface IOpenSectionHack : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE OpenSection(LPMAPIUID, ULONG, LPPROFSECT*) = 0;
};


IOpenSectionHack** ppProfile = (IOpenSectionHack**)((((BYTE*)pServices) + 0x48));

...
In that snippet IOpenSectionHack interface is actualy IMAPIProfile interface.

Code snipped below shows how to open profile section using above interfaces PRProviderInit function

HRESULT OpenProfileSection(
    LPCSTR        ProfileName,        //NULL for obtaining default profile
    LPMAPIUID     lpUID,              //section UID
    ULONG         ulFlags,            //access flags (MAPI_MODIFY,MAPI_CREATE)
    IProfSect**   lppPrfSect)         //pointer to the section
{
    LPIPRPROVIDER profile = NULL;
    LPMAPIPROFILE mapiprofile = NULL;
    HRESULT hResult;
    
    //initialize if we return nothing
    *lppPrfSect = NULL;
    ULONG version = 0;
    
    hResult = PRProviderInit(
        GetModuleHandleW(L"MAPI32.DLL"),
        MAPIGetDefaultMalloc(),
        &MAPIAllocateBuffer,
        &MAPIAllocateMore,
        &MAPIFreeBuffer,
        0,
        CURRENT_SPI_VERSION,
        &version,
        &profile);
    if(SUCCEEDED(hResult))
    {
        ULONG lpDummy = 0;
        ULONG ScrambleSecurityIDSize = 0;
        LPVOID ScrambleSecurityID = NULL;
        
        hResult = profile->OpenProfile(
            (LPMAPISUP)profile,
            (LPSTR*)&ProfileName,
            "",
            MAPI_USE_DEFAULT,
            NULL,
            &ScrambleSecurityIDSize,
            &ScrambleSecurityID,
            &lpDummy,
            &mapiprofile);
        
        if(SUCCEEDED(hResult))
        {
            hResult = mapiprofile->OpenSection(lpUID, ulFlags, lppPrfSect);
            mapiprofile->Release();
        } // if            
        profile->Release();
    } // if
    return hResult;
}

And small piece of code how to use it


    MAPIUID uidInternal = 
    {
        0x92, 0x07, 0xf3, 0xe0,
        0xa3, 0xb1, 0x10, 0x19,
        0x90, 0x8b, 0x08, 0x00,
        0x2b, 0x2a, 0x56, 0xc2
    };

    LPPROFSECT pProfSect = NULL;
    hResult = OpenProfileSection(
        NULL,    //default profile
        &uidInternal,
        MAPI_MODIFY,
        &pProfSect);
    if(SUCCEEDED(hResult))
    {
        pProfSect->Release();
    } // if

To make life easier :) there is a working sample of custom profile provider. It was successfuly tested under some version of Outlook against Personal Folders provider and Exchange provider.

Some words about this sample:

Profiles are stored in the C:\Profiles directory as Windows ini files. File that contains default profile has SYSTEM attribute.
Properties are stored as PropTag=Hex Value. See some snippet below

[8503020000000000C000000000000046] //Section UID
300B0102=1A854565A0D15A4D82590A941218B0CA //Property definition
[65675FC40FEF3748A49C3D4A109486B9]
3D0A001E=6D737073742E646C6C00
3D13001E=7B36343835443236322D433241432D313144312D414433452D3130413043393131433943307D00
3D0F101E=6D737073742E646C6C00
3D0B001E=50535453657276696365456E74727900
30090003=20000000
3D09001E=4D53505354204D5300
3001001E=506572736F6E616C20466F6C6465727300
3D000102=31CBD185ED83B14DB041753168D4A528

Sample implements all property types including multivalue properties.

Important files:
MAPIinternals.h: contains interfaces definitions
ProfileStore.cpp: contains code that reads and writes from/to ini file
ProfSect.cpp: implements IProfSect interface using MAPI based IPropData implementation
PRProvider.cpp: contains IPRProvider implementation.
MAPIProfile.cpp: contains IMAPIProfile implementation.
ProfileManager.cpp: provider entry point

What else should be done for that sample:

  1. Synchronize access for the same profile(file) form different processes
  2. Multithreading synchronization code for all objects
  3. Profile selection dialog
  4. Secure properties (Properties that has IDs in range PROP_ID_SECURE_MIN(0x67F0) – PROP_ID_SECURE_MAX(0x67FF))

 

Authors:

Sergey Golovkin (MAPI33.NET Author)
www.mapi33.adexsolutions.com

Henry Gusakovsky (MAPISpy Author)
www.mapispy.com