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;
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;
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;
pMapiInit.ulFlags = MAPI_NT_SERVICE;
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)
{
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;
CString csMailbox = _T("March2001");
CString csFolder = _T("Top of Personal Folders\\Test Messages");
CString csProcessedFolder =
_T("Top of Personal Folders\\Test Messages\\Processed");
CString csMsg = _T("");
static SizedSPropTagArray(4, spthtProps) =
{
4,
{
PR_ENTRYID,
PR_DISPLAY_NAME,
PR_FINDER_ENTRYID,
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;
hr = m_lpSession->GetMsgStoresTable(0, &ptblMStrs);
if (HR_SUCCEEDED(hr))
{
hr = HrQueryAllRows(ptblMStrs,
(LPSPropTagArray) &spthtProps, NULL, &sosName, 0, &pRows);
if (HR_SUCCEEDED(hr))
{
if(pRows->cRows> 0)
{
for(ind = 0; ind< pRows->cRows; ++ind)
{
if (m_bAbort)
break;
lpVal=
pRows->aRow[ind].lpProps;ASSERT(pRows->aRow[ind].cValues == 4);
ASSERT(lpVal[0].ulPropTag == PR_ENTRYID);
CString csSearchedMBox(lpVal[1].Value.lpszA);
if (csMailbox.CompareNoCase(csSearchedMBox) == 0)
{
cbEIDStore = lpVal[0].Value.bin.cb;
lpEIDStore = (LPENTRYID)lpVal[0].Value.bin.lpb;
hr = m_lpSession->OpenMsgStore(0, cbEIDStore,
lpEIDStore, NULL,MDB_WRITE | MAPI_DEFERRED_ERRORS
| MDB_NO_MAIL, &lpStore);
if (HR_SUCCEEDED(hr))
{
hr = HrMAPIOpenFolderEx(lpStore,
'\\', csProcessedFolder, &lpProcessedFolder);
if (HR_SUCCEEDED(hr))
{
hr = HrMAPIOpenFolderEx(lpStore, '\\', csFolder,&lpFolder);
if (HR_SUCCEEDED(hr))
{
FindEmails(lpFolder, lpProcessedFolder);
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);
}
MAPIFreeBuffer(lpEIDStore);
cbEIDStore = 0;
lpEIDStore = NULL;
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);
}
}
MAPIFreeBuffer(lpVal);
lpVal = NULL;
}
}
else
{
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;
}
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;
SizedSPropTagArray(6, MsgTags) =
{
6,
{
PR_ENTRYID,
PR_MESSAGE_FLAGS,
PR_SENDER_NAME,
PR_BODY,
PR_MESSAGE_DELIVERY_TIME,
PR_SUBJECT
}
};
hr = pfld->GetContentsTable(0, &pmt);
if (hr == S_OK)
{
hr = pmt->SetColumns((LPSPropTagArray)&MsgTags, 0);
if (hr == S_OK)
{
hr = pmt->GetRowCount(0, &ulRows);
if (hr == S_OK)
{
if (ulRows > 0)
{
Start_Recurse_Here:
hr = pmt->QueryRows(ulRows, 0, &lpRows);
if (hr == S_OK)
{
for (u=0; u <ulRows; u++)
{
if (m_bAbort)
break;
lpVal = lpRows->aRow[u].lpProps;
if (lpRows->aRow[u].cValues == 6)
{
ASSERT(lpVal[0].ulPropTag == PR_ENTRYID);
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");
BOOL bProcessed = ProcessMsg(csBody,
csFrom, csDTime, csSubject);
LPENTRYID lpEid = (LPENTRYID)lpVal[0].Value.bin.lpb;
ULONG cbEid = lpVal[0].Value.bin.cb;
hr = pfld->OpenEntry(cbEid,
(LPENTRYID)lpEid,
NULL,
MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN*)&lpMessage);
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)
{
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
{
goto Start_Recurse_Here;
}
}
MAPIFreeBuffer(lpVal);
lpVal = NULL;
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).