Putting a Path in MAPISVC.INF

As you know, when you install a new provider on a system, you have to update MAPISVC.INF to point it to the new provider. There are a few standard properties set during this configuration which tell MAPI where to find your DLL. One is PR_SERVICE_DLL_NAME, set in the Message Service section, and the other is PR_PROVIDER_DLL_NAME, set in the Service Provider section. For both properties, you are expected to set the name of your provider’s DLL (minus the “32” suffix”). MAPI will then load your provider by looking for it on the path.

What if the path isn’t good enough? What if, like any other modern application, you want to drop your provider over in Program Files and not dirty the path? According to the MAPI documentation, you shouldn’t be able to do that. However, it turns out that, with a few restrictions, Outlook’s MAPI can deal with full paths to MAPI providers. Outlook development has agreed to support this for Outlook 2010 and higher.

Here are the particulars:

  • When registering your provider in MAPISVC.INF, you should put the full path to the provider in PR_SERVICE_DLL_NAME and PR_PROVIDER_DLL_NAME.
  • Further, in a store provider, you will from time to time generate entry IDs using WrapStoreEntryID, which takes as a parameter the name of your provider. If you’re using full paths in MAPISVC.INF, you must use the same path in WrapStoreEntryID.
  • In both cases, this full path must be without the “32” suffix, as MAPI will continue to append it before looking for your file. So if you register the path “c:mypathmyprovider.dll”, MAPI will try to load “c:mypathmyprovider32.dll”.
  • Because Outlook’s MAPI was not originally designed with full paths in mind, it does this insertion of the “32” by looking for the first period in the string. This means that paths which contain other periods cannot work. So you cannot use paths such as “c:my.groovy.pathmyprovider.dll” or “c:mypathmy.groovy.provider.dll”. Update: this was fixed in Outlook 2013 (http://support.microsoft.com/kb/2817503) and Outlook 2010 (http://support.microsoft.com/kb/2760764).
  • The path you use may be converted to and from Unicode using the code page provided by GetACP. You will see failures if you choose a path which contains characters cannot survive such a roundtrip through MultiByteToWideChar/WideCharToMultiByte.

To demonstrate this, I’ve updated the Wrapped PST sample over on Codeplex. The magic happens in MergeWithMAPISVC and GenerateProviderPath.


Comments (3)

  1. Kim Groves says:

    The code from Wrapped PST Sample can also be applied to the Sample Address Book Provider.
    Just watch out for this line in UTILS.H

    It should be
    for an Address Book Provider.
    The rest is pretty straight forward.
    I also like to uncomment the line
    in the _Output() function if I want to check what is happening in MergeWithMAPISVC using Dbgview.exe

  2. Kim Groves says:

    When you are testing your project in Visual Studio, you might like to include this Post Build Event in your project properties :
    rundll32 $(TargetPath) MergeWithMAPISVC
    Now when you build your project, the entries in MAPISVC.INF will refresh automatically. I realise that the MAPISVC.INF file will not normally change. This step can be very helpful if you are using SVN with multiple development machines. Now you don’t have to wonder if MAPISVC.INF is correct for the current project version. Building the project will tidy that up for you.
    (Note: Outlook will look for your Provider DLL in your project Debug output folder when you use %targetpath%)
    Please be careful if you are also testing a setup program with your Provider.
    Once the provider has been installed in Outlook, Outlook will load the DLL file that was copied in from MAPISVC.INF.
    If you use MergeWithMAPISVC and update the path to the DLL file, this is not automatically updated into the Outlook profile.
    You should remove the Provider within Outlook (which requires an application restart) then when you re-add the provider, the updated path in MAPISVC.inf will be used. This is particularly important if you want the Visual Studio debugger to be triggered on breakpoints in the Provider code.
    (These comments are based on personal experience so please forgive any incorrect phrases as I am an end-user of MAPI.)

  3. Kim Groves says:

    I have also altered UTILS.H and UTILS.CPP so that the entries in MAPISVC.INF are automatically updated to the correct DLL shortened file path instead of using the hard coded path C:\AddrBook\ in the Wrapped PST Sample code. This ties into my other comments about calling MergeWithMAPISVC from the post-build event.

    // UTILS.H
    // These strings will be inserted into MAPISVC.INF exactly as specified here. Just add and remove entries as necessary
    // %s will be expanded to the current short name path of the DLL that contains this code
    // ie if you use rundll32 c:\addressbook\sabp32.dll MergeWithMAPISVC
    // Then %s will be c:\addres~1\
    static SERVICESINIREC aWrapPSTServicesIni[] =
    {_T(“Services”), _T(“SABP”), 0L, _T(“Sample Contacts”)},

    { _T(“SABP”), _T(“Providers”), 0L, _T(“S_ABP”) },
    { _T(“SABP”), _T(“PR_DISPLAY_NAME”), 0L, _T(“Sample Contacts”) },
    {_T(“SABP”), _T(“PR_SERVICE_DLL_NAME”), 0L, _T(“%ssabp.dll”)},
    {_T(“SABP”), _T(“PR_SERVICE_ENTRY_NAME”), 0L, _T(“ServiceEntry”)},
    {_T(“SABP”), _T(“PR_SERVICE_SUPPORT_FILES”), 0L, _T(“%ssabp.dll”)},
    {_T(“SABP”), _T(“PR_SERVICE_DELETE_FILES”), 0L, _T(“%ssabp.dll”)},
    {_T(“SABP”), _T(“WIZARD_ENTRY_NAME”), 0L, _T(“WizardEntry”) },

    {_T(“S_ABP”), _T(“PR_PROVIDER_DLL_NAME”), 0L, _T(“%ssabp.dll”)},
    {_T(“S_ABP”), _T(“PR_DISPLAY_NAME”), 0L, _T(“Sample Contacts”)},
    {_T(“S_ABP”), _T(“PR_PROVIDER_DISPLAY”), 0L, _T(“Sample Contacts”)},

    {NULL, NULL, 0L, NULL}
    // ..
    // UTILS.CPP
    // $–HrSetProfileParameters———————————————-
    // Add values to MAPISVC.INF
    // —————————————————————————–
    STDMETHODIMP HrSetProfileParameters(SERVICESINIREC *lpServicesIni)
    HRESULT hRes = S_OK;
    TCHAR szSystemDir[MAX_PATH+1] = {0};
    TCHAR szServicesIni[MAX_PATH+12] = {0}; // 12 = space for “MAPISVC.INF”
    UINT n = 0;
    TCHAR szPropNum[10] = {0};
    TCHAR dllpath[MAX_PATH];
    HMODULE hm = NULL;
    TCHAR szDllPathValue[MAX_PATH + 1] = { 0 }; // Check for Key Values that need to be expanded to include the current DLL path
    TCHAR dllpath_buffer[_MAX_PATH]; // Split the running DLL Path into components and recombine into dllpath after shortening
    TCHAR dlldrive[_MAX_DRIVE];
    TCHAR dlldir[_MAX_DIR];
    TCHAR dllfname[_MAX_FNAME];
    TCHAR dllext[_MAX_EXT];
    errno_t err;
    long length = 0;


    if (!lpServicesIni) return MAPI_E_INVALID_PARAMETER;


    if (!szServicesIni[0])
    UINT uiLen = 0;
    uiLen = GetSystemDirectory(szSystemDir, CCH(szSystemDir));
    if (!uiLen)
    return MAPI_E_CALL_FAILED;

    Log(true,_T(“Writing to this directory: \”%s\”\n”),szSystemDir);

    hRes = StringCchPrintf(
    Log(true,_T(“Writing to this file: \”%s\”\n”),szServicesIni);
    // Get the Path to the DLL
    int ret = GetLastError();
    Log(true, _T(“GetModuleHandle returned %d\n”), ret);
    GetModuleFileNameW(hm, dllpath, sizeof(dllpath));
    err = _wsplitpath_s(dllpath, dlldrive, _MAX_DRIVE, dlldir, _MAX_DIR, dllfname,
    _MAX_FNAME, dllext, _MAX_EXT);
    if (err != 0)
    Log(true, _T(“Error splitting the path. Error code %d.\n”), err);
    // Combine the drive and directory back into the path that we need
    wsprintf(dllpath_buffer, _T(“%s%s”), dlldrive, dlldir);
    // MAPISVC.INF requires the shortened 8.3 file path
    length = GetShortPathNameW(dllpath_buffer, dllpath, MAX_PATH);
    Log(true, _T(“DLL Path: \”%s\”\n”), dllpath);
    // Loop through and add items to MAPISVC.INF
    n = 0;

    while(lpServicesIni[n].lpszSection != NULL)
    LPTSTR lpszProp = lpServicesIni[n].lpszKey;
    LPTSTR lpszValue = lpServicesIni[n].lpszValue;

    // Switch the property if necessary

    if ((lpszProp == NULL) && (lpServicesIni[n].ulKey != 0))

    hRes = StringCchPrintf(

    if (SUCCEEDED(hRes))
    lpszProp = szPropNum;

    // If the value needs to have the DLL path expanded, do so
    hRes = StringCchPrintf(

    // Write the item to MAPISVC.INF

    Log(true, _T(“%s -> %s = %s\n”), lpServicesIni[n].lpszSection, lpszProp, szDllPathValue);

    // Flush the information – ignore the return code
    WritePrivateProfileString(NULL, NULL, NULL, szServicesIni);

    return hRes;