HOWTO: MAPI Enable a Windows NT Service

ID: Q197820

The information in this article applies to:

SUMMARY

This article describes how to MAPI-enable a Microsoft Windows NT service. The focus is on what considerations are necessary for MAPI to behave well inside a Windows NT Service. Therefore, creating a Windows NT service will not be discussed. For more information on creating a Windows NT service application, please see the article "Creating a Simple Windows NT Service in C++" in the October 1998 release of the Microsoft Developer Network CD. This article comes complete with an overview, detailed discussion, and a working sample of a Windows NT service.

MORE INFORMATION

In the article mentioned previously, the author implements a class called CNTService. This class definition has two member methods of significance to this topic. These methods are OnInit and Run and are defined as follows:

   BOOL CMyService::OnInit()
   {
     // Read the registry parameters.
     // Try opening the registry key:
     // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet
     // \Services\<AppName>\Parameters

     HKEY hkey;
     char szKey[1024];
     strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\");
     strcat(szKey, m_szServiceName);
     strcat(szKey, "\\Parameters");

     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                      szKey,
                      0,
                      KEY_QUERY_VALUE,
                      &hkey) == ERROR_SUCCESS)
     {
        // Yes we are installed.
        DWORD dwType = 0;
        DWORD dwSize = sizeof(m_iStartParam);
        RegQueryValueEx(hkey,
                        "Start",
                        NULL,
                        &dwType,
                        (BYTE*)&m_iStartParam,
                        &dwSize);
         dwSize = sizeof(m_iIncParam);
        RegQueryValueEx(hkey,
                       "Inc",
                       NULL,
                       &dwType,
                       (BYTE*)&m_iIncParam,
                       &dwSize);
        RegCloseKey(hkey);
     }

     // Set the initial state.
     m_iState = m_iStartParam;

     return TRUE;
   }

   void CNTService::Run()
   {
     DebugMsg("Entering CNTService::Run()");

     while (m_bIsRunning) {
        DebugMsg("Sleeping...");
        Sleep(5000);
     }

     // Nothing more to do.
     DebugMsg("Leaving CNTService::Run()");
   }

The OnInit method is where the service initializes itself and does all the preparation work for preparing to run. For our purposes, this is the most important method because this is where you would add code to initialize the MAPI subsystem and logon to a session.

The Run method is where all of the work of the service is done. After the service is initialized and fully loaded into memory, this method is called. The While loop keeps the service loaded into memory until some outside force sets a variable - m_IsRunning - to FALSE. This is where you would add code to perform the messaging functionality desired for this service. The way this particular service is set up, you could add code that would send a piece of mail every 5 seconds. There are thousands of different things that this service could do, this is just one example of what can be done with this particular service.

You can modify the code in the OnInit method so that as soon as the OnInit method executes, the MAPI subsystem initialized so that when you enter the Run method a MAPI session object is created. The modified version of this method could look very similar to the following snippet.

NOTE: You can copy this code and replace the existing methods of the Windows NT Service application mentioned previously. This code will not compile or link outside the context of the article mentioned previously without significant changes.

Sample Code

   BOOL CMyService::OnInit()
   {
     // Read the registry parameters.
     // Try opening the registry key:
     // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet
     // \Services\<AppName>\Parameters

     HKEY hkey;
     char szKey[1024];
     strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\");
     strcat(szKey, m_szServiceName);
     strcat(szKey, "\\Parameters");

     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                      szKey,
                      0,
                      KEY_QUERY_VALUE,
                      &hkey) == ERROR_SUCCESS)
     {
        // Yes we are installed.
        DWORD dwType = 0;
        DWORD dwSize = sizeof(m_iStartParam);
        RegQueryValueEx(hkey,
                        "Start",
                        NULL,
                        &dwType,
                        (BYTE*)&m_iStartParam,
                        &dwSize);

        dwSize = sizeof(m_iIncParam);
        RegQueryValueEx(hkey,
                       "Inc",
                       NULL,
                       &dwType,
                       (BYTE*)&m_iIncParam,
                       &dwSize);
        RegCloseKey(hkey);
     }

     StartMAPI ( &m_pSession );

     // Set the initial state.
     m_iState = m_iStartParam;

     return TRUE;
   }

   HRESULT CNTService::StartMAPI ( LPMAPISESSION *pSess )
   {
     // Declare and initialize any variables used by MAPI.
     HRESULT hRes = S_OK;
     MAPIINIT_0 pMapiInit = { MAPI_INIT_VERSION, MAPI_NT_SERVICE };
     ULONG ulFlags = 0L;
     char pProfile[64];
     LPMAPISESSION pSvcSess = NULL;

     // Initialize MAPI.
     hRes = MAPIInitialize ( &pMapiInit );

     // If MAPIInitialize succeeds, create a session object.
     if ( !FAILED ( hRes ) )
     {
        ulFlags = MAPI_NO_MAIL |
                  MAPI_NEW_SESSION |
                  MAPI_EXPLICIT_PROFILE |
                  MAPI_NT_SERVICE ;

        hRes = MAPILogonEx ( 0L, pProfile, NULL, ulFlags, &pSvcSess);
     }

     if ( !FAILED ( hRes ) )
     {
        *pSess = pSvcSess;
     }

     return hRes;
   }

Because neither StartMAPI() nor m_pSession are already defined in the CNTService class, you need to add this method and property to the class definition so the compiler knows to expect them up front. Also, this Windows NT Service needs to be aware of all of the special data types that MAPI supports. To do this add the following lines of code to the class definition found in the accompanying header file:

   HRESULT StartMAPI ( LPMAPISESSION * );
   LPMAPISESSION m_pSession;

Add the following line of code to the same header file in the file scope section at the top where the other include statement is found:

   #include <mapix.h>

There are a few things in this MAPI code that are not necessary for normal desktop MAPI clients to do but are necessary for MAPI clients that run in a Windows NT Service. The first major difference is the call to the MAPIInitialize() function. You pass in a MAPIINIT_0 structure that lets the MAPI subsystem know that MAPI is going to execute in a Windows NT Service. This occurs in the OnInit because this is where all initialization should occur. It would be confusing to move this code to the Run method because by the time your service is in the Run method, it appears that the service has passed all initialization tests. Receiving an initialization error in the Run method would be misleading to the user.

The next major difference is in the call to the MAPILogonEx() fucntion. You must specify two flags that you normally do not or should not use from a desktop application. The two flags are:

The MAPI_NO_MAIL flag tells the MAPI spooler, Mapisp32.exe, not to load. The MAPI_NT_SERVICE flag lets MAPILogonEx know that the session will run in the context of a Windows NT Service. Another important point to make is that the lpszProfileName parameter must contain a string that maps to a MAPI profile name that is visible to the Windows NT Service.

Windows NT Services do not, by default, run in the same security context as an interactively logged on user. Because it runs in a different security context, it has access to a different registry hive than an interactively logged on user. MAPI stores all profile information for each user in separate hives in the system registry. A Windows NT Service can only see MAPI profiles that are created by the service itself or that have been stored in the registry hive that maps to the security context in which the service currently runs.

Although the preceding code does not demonstrate any MAPI calls beyond logging on to a MAPI session, it is important to remember one following point:

   Windows NT Services are expected to run unattended.

If a function is called that displays a dialog box that requires user input, the service effectively stops running. For this reason, it is important to consider the MAPI calls you make. Many of these calls can present a dialog box to the user. Each call should provide a mechanism for preventing the display of a dialog box. Windows NT services should always set this flag.

Even so, some service providers do not always honor their flags, and may pop up a dialog box to prompt for user credentials or other configuration information, particularly if that information is not stored in the profile. Non-interactive applications must investigate carefully the MAPI provider or providers they intend to use, to verify that the profile can be configured programmatically and that logon can take place without unwanted dialog boxes. This is a design issue that will not be fixed. Vigilance is required on the part of programmers implementing MAPI service providers or Windows NT services that use MAPI.

REFERENCES

October 1998 Microsoft Developer Network CD article "Creating a Simple Windows NT Service in C++"

Additional query words:

Keywords          : kbMAPI kbMsg kbMAPI100 
Version           : WINDOWS:1.0
Platform          : WINDOWS
Issue type        : kbhowto

Last Reviewed: December 23, 1998