Click here to Skip to main content
Click here to Skip to main content
Go to top

Process Microsoft® Outlook® Messages Automatically (via App. or a NT Service).

, 28 Apr 2001
Rate this:
Please Sign up or sign in to vote.
An article showing you how to process (Outlook®) E-mail messages automatically by using an MFC dialog or NT service.

Introduction

First off, I tried to comment the code as much as possible to save you folks of the pains I went through. These programs are pretty simple and are meant for demonstration purposes only and should not be put into a production environment without fully testing first (as always)!!!

I started having to look into processing E-mail messages because we had some mailboxes that had a whole lot of mails (50,000+). These mails needed to be processed and put into a database (I used SQL 7.x/2000). This article doesn't touch on the database side of the house. After a few weeks and a lot of questions, I created an application to do it, then after successful tests, I put the code into an NT Service, therefore automating the reading of the mailbox and the processing of the mails.

The examples first start with the starting of MAPI (in the MFC Dialog):

BOOL CProcessMBApp::StartMAPI(void)
{
  CString  csErr = _T("");
  BOOL     bRet = TRUE;
  HRESULT  hr = S_OK;

  //
  // If you do NOT want to use the default profile then use this below...
  //
  //    FLAGS flFlag = MAPI_NO_MAIL | MAPI_NEW_SESSION;
  //
  FLAGS flFlag = MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION;

  hr = MAPIInitialize(NULL);
  if (!HR_SUCCEEDED(hr))
  {
    csErr = _T("MAPIInitialize failed...");
    MessageBox(NULL, csErr, _T("MAPI Error"), MB_OK | MB_ICONSTOP);
    m_bInitialized = FALSE;
    bRet = FALSE;
  }
  else
  {
    m_bInitialized = TRUE;
    //
    // If you do NOT use the default profile then use this below...
    //
    //    hr = MAPILogonEx(0L, (LPTSTR)"loginID goes here", 
    //                     (LPTSTR)"", flFlag, &m_lpSession);
    //
    hr = MAPILogonEx(0L, (LPTSTR)NULL, (LPTSTR)NULL, flFlag, &m_lpSession);
    if (!HR_SUCCEEDED(hr))
    {
      csErr = _T("MAPILogonEx failed...");
      MessageBox(NULL, csErr, _T("MAPI Error"), MB_OK | MB_ICONSTOP);
      m_bInitialized = FALSE;
      bRet = FALSE;
    }
  }

  return bRet;

}

For the above to be used in an NT Service, the following changes need to be made:

  MAPIINIT_0 pMapiInit;
  pMapiInit.ulVersion = MAPI_INIT_VERSION;
  // This is a MUST !!!!!! ==
  //                        ||
  //                        \/
  pMapiInit.ulFlags = MAPI_NT_SERVICE;
  
  // This is also a MUST !!!!!! ===========================
  //                                                      ||
  //                                                      \/
  FLAGS flFlag = MAPI_NO_MAIL | MAPI_NEW_SESSION | MAPI_NT_SERVICE;
  hr = MAPIInitialize(&pMapiInit);

Next comes the processing of the E-mail (actually, it's preparation for it)...

NOTE: To implement this into an NT Service is also a simple "Cut | Paste"...Ok, Ok, you shouldn't use the MessageBox function...replace it with something that logs because you don't want UI's in an NT service:

void CProcessMBApp::ProcessEmails(void)
{
  //***********************************************************
  // START - looking at our Mails in the Mailbox
  //***********************************************************

  LPSPropValue lpVal = NULL;
  LPENTRYID    lpEIDStore = NULL;
  LPMAPITABLE  ptblMStrs = NULL;
  LPMDB        lpStore = NULL;
  LPMAPIFOLDER lpFolder = NULL;
  LPMAPIFOLDER lpProcessedFolder = NULL;
  LPMAPIFOLDER lpParentFolder = NULL;

  ULONG        cbEIDFolder = 0;
  LPENTRYID    lpEIDFolder = NULL;

  HRESULT      hr = S_OK;
  ULONG        cbEIDStore = 0;
  ULONG        ulUIParam = 0;
  ULONG        ulFlags = 0;
  ULONG        ulObjType = 0;
  ULONG        ulResult = 0;

  CString csError = _T("");
  char szErr[64];

  if (m_bAbort)
    return;

  if (m_lpSession == NULL)
    StartMAPI();

  m_bIsRunning = TRUE;

  //////////////////////////////////////////////////////////
  //
  // My Mailbox...as it is shown in OutLook...
  //
  // CString csMailbox = _T("Mailbox - Madden, Daniel");
  //
  // BELOW is a "Personal Folder"
  //////////////////////////////////////////////////////////
  CString csMailbox = _T("March2001");

  //////////////////////////////////////////////////////////
  // 
  // The structure of the folder to process...
  //
  // NOTE: "Top of Information Store" must be the ROOT.
  // CString csFolder = 
  //        _T("Top of Information Store\\Inbox\\Test Messages");
  //
  // NOTE: "Top of Personal Folders" is a must when looking
  //       in "Personal Folders".
  //
  // BELOW is a path (in "Personal Folder") 
  // to the "Test Messages" folder...
  //////////////////////////////////////////////////////////
  CString csFolder = _T("Top of Personal Folders\\Test Messages");

  //////////////////////////////////////////////////////////
  // The structure of the folder to put the processed mails.
  //
  // NOTE: "Top of Information Store" must be the ROOT.
  // CString csProcessedFolder = 
  //  _T("Top of Information Store\\Inbox\\Test Messages\\Processed");
  //
  // NOTE: "Top of Personal Folders" is a must when looking
  //       in "Personal Folders".
  //
  // BELOW is a path (in "Personal Folder") to the "Processed" folder...
  //////////////////////////////////////////////////////////
  CString csProcessedFolder = 
    _T("Top of Personal Folders\\Test Messages\\Processed");

  CString csMsg = _T("");

  // The SizedSPropTagArray macro creates a named SPropTagArray structure 
  // that includes a specified number of property tags.
  static SizedSPropTagArray(4, spthtProps) =
    {
      4,
      {
        // MAPI entry identifier used to open and edit 
        // properties of a particular MAPI object...
        PR_ENTRYID, 
        // contains the display name for a given MAPI object...
        PR_DISPLAY_NAME, 
        // entry identifier for the folder in which
        // search results are typically created.
        PR_FINDER_ENTRYID,
        // TRUE if a folder contains subfolders....
        PR_SUBFOLDERS
      }
    };

  ptblMStrs = NULL;
  UINT ind;
  LPSRowSet pRows = NULL;
  static SSortOrderSet sosName;

  sosName.cSorts = 1;
  sosName.cCategories = 0;
  sosName.cExpanded = 0;
  sosName.aSort[0].ulPropTag = PR_DISPLAY_NAME;
  sosName.aSort[0].ulOrder = TABLE_SORT_ASCEND;

  // a table with information about all of
  // the message stores in the session profile.
  hr = m_lpSession->GetMsgStoresTable(0, &ptblMStrs);
  if (HR_SUCCEEDED(hr))
  {
    // retrieves all rows of a table.
    hr = HrQueryAllRows(ptblMStrs, 
      (LPSPropTagArray) &spthtProps, NULL, &sosName, 0, &pRows);
    if (HR_SUCCEEDED(hr))
    {
      //
      // Make sure we have some rows to look through...
      //
      // NOTE: The rows are the root entries found in the Malbox..
      //
      if(pRows->cRows>  0)
      {

        // Loop through the rows...
        for(ind =  0;  ind< pRows->cRows; ++ind)
        {
          // Get out of here if cancelled...
          if (m_bAbort)
            break;
          
          lpVal= 
            pRows->aRow[ind].lpProps;ASSERT(pRows->aRow[ind].cValues == 4);
          ASSERT(lpVal[0].ulPropTag == PR_ENTRYID);
          
          // This will contain the mailbox name...
          CString csSearchedMBox(lpVal[1].Value.lpszA);
          
          // Check if this is the Mailbox we want to process...
          if (csMailbox.CompareNoCase(csSearchedMBox) == 0)
          {
            cbEIDStore = lpVal[0].Value.bin.cb;
            lpEIDStore = (LPENTRYID)lpVal[0].Value.bin.lpb;
            
            // open a message store and returns an IMsgStore
            // pointer for further access.
            hr = m_lpSession->OpenMsgStore(0, cbEIDStore, 
              lpEIDStore, NULL,MDB_WRITE | MAPI_DEFERRED_ERRORS 
                                     | MDB_NO_MAIL, &lpStore);
            if (HR_SUCCEEDED(hr))
            {
              // open the "csProcessedFolder" folder in the information
              // (or Personal Folders) store from the hierarchical path
              // name of the folder.
              hr = HrMAPIOpenFolderEx(lpStore, 
                  '\\', csProcessedFolder, &lpProcessedFolder);
              if (HR_SUCCEEDED(hr))
              {
                // open the "csFolder" folder in the information
                // (or Personal Folders) store from the hierarchical path
                // name of the folder.
                hr =  HrMAPIOpenFolderEx(lpStore, '\\', csFolder,&lpFolder);
                if (HR_SUCCEEDED(hr))
                {
                  // Now we are ready to process the emails...
                  // Let's go and find emails to process...
                  FindEmails(lpFolder, lpProcessedFolder);
                  
                  //UlRelease(lpFolder);
                  //
                  // UlRelease (above) returns the value returned by
                  // the IUnknown::Release method, which can be
                  // equal to the reference count for the object
                  // to be released.
                  //
                  // NOTE: This function may not be supported
                  // in future versions of MAPI.
                  //
                  // Decrement the reference count for the calling
                  // interface on a object. If the reference count on
                  // the object falls to 0, the object is freed from
                  // memory.
                  //
                  // I use (the supported) IUnknown::Release...
                  lpFolder->Release();
                  lpFolder = NULL;
                }
                else
                {
                  sprintf(szErr,"Error =  
                     Unable to open mailbox\\folder: [%s\\%s]\n", 
                     csMailbox, csFolder);
                  MessageBox(NULL, szErr, _T("MAPI Error!"), 
                     MB_OK | MB_ICONSTOP);
                }
                
                lpProcessedFolder->Release();
                lpProcessedFolder = NULL;
              }
              else
              {
                sprintf(szErr,"Error = 
                    Unable to open mailbox\\folder: [%s\\%s]\n", 
                    csMailbox, csProcessedFolder);
                MessageBox(NULL, szErr, _T("MAPI Error!"), 
                    MB_OK | MB_ICONSTOP);
              }
              
              // The call to MAPIFreeBuffer to free a particular buffer
              // must be made as soon as a client (or provider) is finished
              // using this buffer. Simply calling the MAPILogoff function
              // at the end of a MAPI session does not automatically release
              // memory buffers.
              MAPIFreeBuffer(lpEIDStore);
              cbEIDStore = 0;
              lpEIDStore = NULL;
              
              //UlRelease(lpStore);
              lpStore->Release();
              lpStore = NULL;
            }
            else
            {
              sprintf(szErr,"Error = 
                 Unable to open mailbox: [%s]\n", csMailbox);
              MessageBox(NULL, szErr, _T("MAPI Error!"), 
                 MB_OK | MB_ICONSTOP);
            }
          }
          
          // For information on MAPIFreeBuffer, see above info...
          MAPIFreeBuffer(lpVal);
          lpVal = NULL;
        }
      }
      else //$ No stores
      {
        sprintf(szErr,"Error = No message stores in the profile.\n");
        MessageBox(NULL, szErr, _T("MAPI Error!"), MB_OK | MB_ICONSTOP);
      }
    }
    else
    {
      sprintf(szErr,"Error = HrQueryAllRows failed.\n");
      MessageBox(NULL, szErr, _T("MAPI Error!"), MB_OK | MB_ICONSTOP);
    }
  }
  else
  {
    sprintf(szErr,"Error = GetMsgStoresTable failed.\n");
    MessageBox(NULL, szErr, _T("MAPI Error!"), MB_OK | MB_ICONSTOP);
  }

  ptblMStrs = NULL;
  hr = NULL;
  cbEIDStore = 0;
  ulUIParam = 0;
  ulFlags = 0;
  ulObjType = 0;
  ulResult = 0;
  
  lpVal = NULL;
  lpEIDStore = NULL;
  ptblMStrs = NULL;
  lpStore = NULL;
  lpFolder = NULL;
  lpProcessedFolder = NULL;
  hr = NULL;
  
  m_bIsRunning = FALSE;
  
  //***********************************************************
  // DONE - looking at Mails in the Mailbox
  //***********************************************************
}

Next comes the finding of the E-mails and processing...

NOTE: To implement this into an NT Service is also a simple "Cut | Paste"...Ok, Ok, you shouldn't use the MessageBox function...replace it with something that logs because you don't want UI's in an NT service:

void CProcessMBApp::FindEmails(LPMAPIFOLDER pfld, 
                              LPMAPIFOLDER pfldProcessed) 
{
  LPMAPITABLE  pmt = NULL;
  LPSPropValue lpVal = NULL;
  LPSRowSet    lpRows = NULL;
  LPMESSAGE    lpMessage = NULL;
  
  HRESULT      hr = NOERROR;
  ULONG        ulRows = 0;
  UINT         u = 0;
  ULONG        ulObjType = 0L;
  
  char szErr[64];
  int i = 0;
  
  if (m_bAbort)
    return;

  // For information on SizedSPropTagArray,
  // see above ProcessEmails Function...
  SizedSPropTagArray(6,  MsgTags) = 
  {
    6,
    {
      // MAPI entry identifier used to open and edit
      // properties of a particular MAPI object...
      PR_ENTRYID,              
      // contains read/unread flags
      PR_MESSAGE_FLAGS,        
      // contains the message sender's display name.
      PR_SENDER_NAME,          
      // contains the message text.
      PR_BODY,
      // contains the date and time a message was delivered.
      PR_MESSAGE_DELIVERY_TIME,
      // contains the full subject of a message.
      PR_SUBJECT  
    }
  };
  
  // returns a pointer to the container's contents table.
  hr = pfld->GetContentsTable(0, &pmt);
  if (hr == S_OK)
  {
    // defines the particular properties and order
    // of properties to appear as
    // columns in the table.
    hr = pmt->SetColumns((LPSPropTagArray)&MsgTags, 0);
    if (hr == S_OK)
    {
      // Get the total number of rows in the table.
      hr = pmt->GetRowCount(0, &ulRows);
      if (hr == S_OK)
      {
        // Make sure we have rows...
        if (ulRows > 0)
        {
Start_Recurse_Here:

          //
          // returns one or more rows from a table, beginning at the
          // current cursor position.
          //
          hr = pmt->QueryRows(ulRows, 0, &lpRows);
          if (hr == S_OK)
          {
            // Loop through the rows...
            for (u=0; u <ulRows; u++)
            {
              // Check if we need to leave (user Cancelled?)
              if (m_bAbort)
                break;
                
              lpVal = lpRows->aRow[u].lpProps;
              if (lpRows->aRow[u].cValues == 6)
              {
                ASSERT(lpVal[0].ulPropTag == PR_ENTRYID);
                
                // Use this if you are sure all messages being processed
                // contain a message BODY !!!!
                //
                // ASSERT(lpVal[3].ulPropTag == PR_BODY);
                
                CString csFrom(lpVal[2].Value.lpszA);
                CString csSubject(lpVal[5].Value.lpszA);
                CString csBody(lpVal[3].Value.lpszA);
                
                CTime ct(lpVal[4].Value.ft);
                CString csDTime = ct.FormatGmt("%d/%m/%Y %H:%M:%S");
                
                //
                // Process the message...
                //
                //  I am only  filling the ListCtrl...you  could do more...
                //
                BOOL bProcessed = ProcessMsg(csBody, 
                         csFrom, csDTime, csSubject);
                
                LPENTRYID lpEid = (LPENTRYID)lpVal[0].Value.bin.lpb;
                ULONG cbEid = lpVal[0].Value.bin.cb;
                
                // opens an object within the container,
                // returning an interface
                // pointer for further access.
                hr = pfld->OpenEntry(cbEid, 
                               (LPENTRYID)lpEid,
                               NULL,
                               MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
                               &ulObjType,
                               (LPUNKNOWN*)&lpMessage);
                
                // Make sure it is a MAPI Message we are looking at!
                ASSERT(MAPI_MESSAGE = = ulObjType);
                
                if (hr == S_OK)
                {
                  ENTRYLIST el;
                  SBinary sb;
                  sb.cb = lpVal[0].Value.bin.cb;
                  sb.lpb = (LPBYTE)lpVal[0].Value.bin.lpb;
                  el.cValues = 1;
                  el.lpbin = &sb;
                  
                  if (bProcessed)
                  {
                    //
                    // Indicate this message has been 
                    //read by calling the "SetReadFlag" below...
                    //
                    //lpMessage->SetReadFlag(SUPPRESS_RECEIPT | 
                    //     MAPI_DEFERRED_ERRORS);
                    //
                    // NOTE: I am just getting the information, then moving
                    // the messages to a different directory!
                    //
                    pfld->CopyMessages(&el, NULL, 
                       (LPVOID)pfldProcessed, NULL, NULL, MESSAGE_MOVE);
                  }
                  
                  lpMessage->Release();
                }
                else
                {
                  sprintf(szErr,"Warning = OpenEntry()=> FAILED!\n");
                  MessageBox(NULL, szErr, _T("MAPI Error!"), 
                                           MB_OK | MB_ICONSTOP);
                  continue;
                }
              }
              else
              {
                //
                // recurse through the messages
                //
                // The reason we are doing this, can be found in the
                // IMAPITable::GetRowCount Function Information in MSDN
                // under "Notes to Callers"...it states:
                //
                // Use GetRowCount to find out how many rows a table holds
                // before making a call to the IMAPITable::QueryRows method
                // to retrieve the data. If there are less than twenty rows
                // in the table, it is safe to call QueryRows to retrieve
                // the whole table. If there are more than twenty rows in
                // the table, consider making multiple calls to QueryRows 
                // and limit the number of rows retrieved in each call.
                // 
                goto Start_Recurse_Here;
              }
            }
            
            // For information on MAPIFreeBuffer,
            // see above ProcessEmails Function...
            MAPIFreeBuffer(lpVal);
            lpVal = NULL;
            
            // The FreeProws function destroys an SRowSet structure and
            // frees associated memory, including memory allocated for
            // all member arrays and structures.
            FreeProws(lpRows);
            lpRows = NULL;
          }
          else
          {
            sprintf(szErr,"Error = QueryRows => FAILED!\n");
            MessageBox(NULL, szErr, _T("MAPI Error!"), 
                                   MB_OK | MB_ICONSTOP);
          }
        }
      }
      else
      {
        sprintf(szErr,"Error = GetRowCount => FAILED!\n");
        MessageBox(NULL, szErr, _T("MAPI Error!"), 
                                 MB_OK | MB_ICONSTOP);
      }
    }
    else
    {
      sprintf(szErr,"Error = SetColumns => FAILED!\n");
      MessageBox(NULL, szErr, _T("MAPI  Error!"),
                                MB_OK | MB_ICONSTOP);
    }
    
    pmt->Release();
  }
  else
  {
    sprintf(szErr,"Error = GetContentsTable => FAILED!\n");
    MessageBox(NULL, szErr, _T("MAPI Error!"),
                           MB_OK | MB_ICONSTOP);
  }
  
  hr = NOERROR; 
  pmt = NULL; 
  lpVal = NULL;
  lpRows = NULL; 
  lpMessage = NULL;
  ulRows = 0; 
  u = 0; 
  ulObjType = 0L;
  
}

Next, create a function to process the data from the message and use it the way you see fit! I used a dialog box and put the data into a ListCtrl (just to see the data)...here is a small picture of what it looked like:

To install the NT service, simply type ProcMBSvc.exe -i and press enter. Then go and have it "Log on as <whoever you say>". This is so if you need access to the network, you'll get it... (see below)":

Now just start the service and wait 15 seconds (it activates every 15 seconds) and watch it work... You can also impersonate the user running as an NT service if you would like. For more information visit INFO: MAPI and Impersonation in a Windows NT Service.

Acknowledgements

In the NT service, I used the CRotatingLog - A simple rotary text file class by P.J. Arends to log info to a file (C:\ProcMBSvc.log)...nice work!

Updates

  • April 29, 2001: Added the link to impersonate a user (Win2000).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dan Madden
Product Manager
Germany Germany
I have been programming (as a hobby) for 20+ years (Unix C, Scripting, VB, C/C++, C#). I am getting too old to talk about it and been in the Security line of work (both Military/Civilian) for 25+ years.

Comments and Discussions

 
GeneralHrMAPIOpenFolderEx fails with 0x800b0001 Pinmemberatv526-Oct-06 18:34 
QuestionHow to register an idle routine ? [modified] Pinmemberjvsun21-Jun-06 20:54 
Generalchops body msg at 255 Pinmemberleemidgley13-Jun-06 9:19 
AnswerRe: chops body msg at 255 PinmemberDan Madden15-Jun-06 4:59 
Questioncreating .msg files on the scratch? Pinmembervertex_x5-Jun-06 4:17 
GeneralLink Error PinmemberNick Thomas29-Mar-06 0:23 
GeneralRe: Link Error PinmemberNick Thomas29-Mar-06 2:25 
QuestionHow to import contacts from subfolder of personal-contacts in outlook Pinmembertoddor_sturt25-Jan-06 15:20 
Questionhow to add new mail notification Pinmemberrajesh_s763-Jan-06 19:28 
AnswerRe: how to add new mail notification PinmemberDan Madden4-Jan-06 3:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 29 Apr 2001
Article Copyright 2001 by Dan Madden
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid