MapiHTTP and Autodiscover – How to open shared mailboxes in a MAPI application


Opening shared mailboxes in a MAPI application has been an easy thing in the past.
Simply query the primary mailbox for the IID_IExchangeManageStore interface and create an EntryID for the shared mailbox by calling CreateStoreEntryID.
This is a V2 Exchange Store Entry ID, as outlined here:
https://msdn.microsoft.com/en-us/library/ee203516(EXCHG.80).aspx
https://blogs.msdn.microsoft.com/stephen_griffin/2011/07/21/store-entry-id-v2/

Here’s a simple code snippet on how to use the IID_IExchangeManageStore interface:

//Now try to open additional mailboxes
IExchangeManageStore * pManagedStore = NULL;

hRes = lpMDB->QueryInterface(IID_IExchangeManageStore, (void**)&pManagedStore);
if (hRes != S_OK)
{
//error handling and quit
}

ULONG cbeid = 0L;
LPENTRYID lpEID = NULL;

pManagedStore->CreateStoreEntryID(
“/o=ContosoOrg/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=Servers/cn=ContosoSrv1/cn=Microsoft Private MDB”,
“/o=ContosoOrg/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=ContosoUsr1”,
OPENSTORE_HOME_LOGON | OPENSTORE_TAKE_OWNERSHIP,
&cbeid,
&lpEID);

IMsgStore * pMsgStore = NULL;
hRes = lpSession->OpenMsgStore(0, cbeid, lpEID, NULL, MDB_NO_DIALOG | MDB_NO_MAIL | MDB_TEMPORARY | MAPI_BEST_ACCESS, &pMsgStore);

This code works when connecting with RPC_via_TCP, or when using Outlook_Anywhere.

 

With the Exchange Server 2013 and 2016, a third communication protocol has been implemented – MAPI_HTTP, which will be the preferred communication protocol between modern Outlook clients and Exchange.
MAPI_HTTP has a strong dependency on Autodiscover, which is an Exchange service to gather mailbox information.

MAPI_HTTP is documented here:
https://technet.microsoft.com/en-us/library/dn635177(v=exchg.150).aspx

As soon as an Outlook client connects via MAPI_HTTP, this information is stamped into the email profile.
And every MAPI application, which uses this email profile, is using MAPI_HTTP too.

When using the above code snippet to open shared mailboxes, the OpenMsgStore will fail, if that mailbox is unknown to the MAPI subsystem.
It will only work, if this shared mailbox has been opened at least once in an Outlook session by using this email profile, or of this mailbox has been added as ‘additional mailbox’ to the email profile.
This makes opening shared mailboxes for an external MAPI application when using MAPI_HTTP complicated.
The “April 5, 2016, update for Outlook 2013 (KB3114941)” has a solution for that, as it offers a new way for external MAPI applications to use AutoDiscover under the hood to open shared mailboxes.

  • First of all, that Outlook 2013 update KB3114941 has to be installed.
    https://support.microsoft.com/en-us/kb/3114941
  • Second, a registry entry (“AllowAutoDiscoverForNonOutlook“) needs to be set to enable that new functionality.
    This is mentioned here:
    3114974 Update adds ability for non-Outlook MAPI applications to use the Autodiscover service in Outlook 2013
    https://support.microsoft.com/en-us/kb/3114974
  • Third, a new interface and a new Entry_ID generator call needs to be used in your code.
    This new Entry_ID generator call requires two additional parameters/MAPI properties.
    PR_FORCE_USE_ENTRYID_SERVER and PR_PROFILE_USER_SMTP_EMAIL_ADDRESS_W, which is used by the emsmdb provider to call AutoDiscover (under the hood) to gather the required information to be abel to open that shared mailbox.

This interface is documented here:
https://blogs.msdn.microsoft.com/dvespa/2014/01/15/a-new-mapi-interface-is-available-to-let-you-force-connections-to-go-to-a-specific-exchange-server/
Here’s a code snippet which demonstrates the new interface:

//Now try to open additional mailboxes
IExchangeManageStoreEx * pManagedStore = NULL;

hRes = lpMDB->QueryInterface(IID_IExchangeManageStoreEx, (LPVOID*)&pManagedStore);
if (hRes != S_OK)
{
//error handling and quit
}

LPSPropValue         lpspv = nullptr;
MAPIAllocateBuffer((sizeof(SPropValue) * 4), (LPVOID*)&lpspv);
ZeroMemory(lpspv, sizeof(SPropValue) * 4);

lpspv[0].ulPropTag = PR_PROFILE_MAILBOX;
lpspv[0].Value.lpszA = “/o=ContosoOrg/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=ContosoUsr1”;

lpspv[1].ulPropTag = PR_PROFILE_MDB_DN;
lpspv[1].Value.lpszA = “/o=ContosoOrg/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=Servers/cn=ContosoSrv1/cn=Microsoft Private MDB”;

lpspv[2].ulPropTag = PR_FORCE_USE_ENTRYID_SERVER;
lpspv[2].Value.b = false;

lpspv[3].ulPropTag = PR_PROFILE_USER_SMTP_EMAIL_ADDRESS_W;
lpspv[3].Value.lpszW = L”ContosoUsr1@Contoso.com”;

ULONG  cbEID = 0L;
LPENTRYID lpEID = NULL;

hRes = pManagedStore->CreateStoreEntryID2(4, lpspv, OPENSTORE_HOME_LOGON | OPENSTORE_TAKE_OWNERSHIP, &cbEID, &lpEID);
MAPIFreeBuffer(lpspv);

IMsgStore * pMsgStore = NULL;
hRes = lpSession->OpenMsgStore(0, cbEID, lpEID, NULL, MDB_NO_DIALOG | MDB_NO_MAIL | MDB_TEMPORARY | MAPI_BEST_ACCESS, &pMsgStore);


Comments (6)

  1. But if I do autodiscover on my own and retrieve the mailbox server name returned by autodiscover, the old CreateStoreEntryID / OpenMsgStore would still work, right?

  2. Bughunter666 says:

    Well done and many thanks for that – highly appreciated as I actually would have run into the problems you outlined above sooner or later 🙂

  3. Uwe Kurze says:

    Will this new interface IExchangeManageStoreEx also supported in Outlook 2016? And when not (that’s my experience), how can I create a storeEntryId V3? At the moment I calculate this Id by adding the “magic” part on an conventional storeEntryId (created with CreateStoreEntryID). But an api call would be nicer…
    Is storeEntryId V3 the only way to open other users mailbox with Outlook 2016 MAPI?

    1. Pranav says:

      Hi Uwe Kurze, I experienced the same i.e. IExchangeManageStoreEx seems NOT supported with Outlook 2016. Can you please share your logic of creating V3 Store entry ID.
      BTW Iam going to try disabling the MAPI Over HTTP (https://support.microsoft.com/en-us/kb/2937684) and then create a new outlook profile, so I believe the new profile would use RPC Over HTTP and see if CreateStoreEntryID works with Outlook 2016.

      1. Uwe Kurze says:

        As Dave reported the interface IID_IExchangeManageStoreEx was added in Outlook 2016 too.
        (https://blogs.msdn.microsoft.com/dvespa/2014/01/15/a-new-mapi-interface-is-available-to-let-you-force-connections-to-go-to-a-specific-exchange-server/)

        >Dave V – MSFT
        >December 15, 2016 at 12:54 pm

        >This was added in the July 2016 update for Outlook 2016. https://support.microsoft.com/en-us/kb/3115279

      2. Uwe Kurze says:

        This is the “former” code, where I create the entryId_V3:

        MsxStore CMsxSession::OpenMailboxV3(_bstr_t mailboxDN, _bstr_t smtpAddress, ULONG flagsForOpenMailbox, ULONG flags)
        {
        IMsgStorePtr mapiMailbox;

        _openPrivateStore();
        _openManagePrivateStore();

        try
        {
        MsxBinary entryId;
        SBinary* pbinEntryId = entryId->GetRawPointer();

        XXHr(_mapiManagePrivateStore->CreateStoreEntryID(NULL, mailboxDN, flagsForOpenMailbox, &(pbinEntryId->cb), (ENTRYID**)&(pbinEntryId->lpb)));

        int sizeSmtpAddress = smtpAddress.length() * sizeof(WCHAR);

        EntryId_V3 entryId_V3 = { MDB_STORE_EID_V3_MAGIC , sizeof(MDB_STORE_EID_V3) + sizeSmtpAddress + sizeof(WCHAR) + sizeof(WORD) , MDB_STORE_EID_V3_VERSION , sizeof(MDB_STORE_EID_V3) };

        int newSize = pbinEntryId->cb + entryId_V3.Size;
        byte* pba = new byte[newSize];

        ULONG offset = 0;
        memcpy(pba, pbinEntryId->lpb, pbinEntryId->cb); offset += pbinEntryId->cb;
        memcpy(&pba[offset], &entryId_V3, sizeof(MDB_STORE_EID_V3)); offset += sizeof(MDB_STORE_EID_V3);
        memcpy(&pba[offset], (WCHAR*)smtpAddress, sizeSmtpAddress); offset += sizeSmtpAddress;
        memset(&pba[offset], 0, sizeof(WCHAR) + sizeof(WORD)); // two zero bytes from smtpAddress + Reserve

        entryId = MsxBinary(newSize, pba);

        delete[] pba;

        mapiMailbox = _openStoreByEntryIdWithRetry(entryId, flags);
        }
        catch (MsxException&)
        {
        mapiMailbox = NULL;

        throw;
        }

        return MsxStore(mapiMailbox);
        }

        But now I use the “new” interface in outlook 2016 too:

        MsxStore CMsxSession::OpenMailboxEx(_bstr_t mailboxDN, _bstr_t smtpAddress, ULONG flagsForOpenMailbox, ULONG flags)
        {
        IMsgStorePtr mapiMailbox;

        _openPrivateStore();
        _openManagePrivateStoreEx();

        LPSPropValue lpspv = nullptr;
        try
        {
        Trace(_T(“CMsxSession::OpenMailboxEx”), _T(“mailboxDN='” + mailboxDN + “‘ smtpAddress='” + smtpAddress + “‘ flagsForOpenMailbox='” + (_bstr_t)CDxeString::FromLong(flagsForOpenMailbox) + “‘”));

        int numProps = 3;
        XXHr(MAPIAllocateBuffer((sizeof(SPropValue) * numProps), (LPVOID*)&lpspv));
        ZeroMemory(lpspv, sizeof(SPropValue) * numProps);

        lpspv[0].ulPropTag = PR_PROFILE_MAILBOX;
        lpspv[0].Value.lpszA = (LPSTR)mailboxDN;

        lpspv[1].ulPropTag = PR_FORCE_USE_ENTRYID_SERVER;
        lpspv[1].Value.b = false;

        lpspv[2].ulPropTag = PR_PROFILE_USER_SMTP_EMAIL_ADDRESS_W;
        lpspv[2].Value.lpszW = (LPWSTR)smtpAddress;

        MsxBinary entryId;
        SBinary* pbinEntryId = entryId->GetRawPointer();
        XXHr(_mapiManagePrivateStoreEx->CreateStoreEntryID2(numProps, lpspv, flagsForOpenMailbox, &(pbinEntryId->cb), (ENTRYID**)&(pbinEntryId->lpb)));

        Trace(_T(“CMsxSession::OpenMailboxEx”), _T(“openStoreByEntry entryId='” + entryId->ToString() + “‘ flags='” + (_bstr_t)CDxeString::FromLong(flags) + “‘ …”));
        mapiMailbox = _openStoreByEntryIdWithRetry(entryId, flags);
        Trace(_T(“CMsxSession::OpenMailboxEx”), _T(“openStoreByEntry … successfully opened.”));
        }
        catch (MsxException&)
        {
        MAPIFREE(lpspv);
        mapiMailbox = NULL;

        throw;
        }
        MAPIFREE(lpspv);

        return MsxStore(mapiMailbox);
        }

Skip to main content