Writing Simple Exchange Server Client
| Nik Okuntseff |
MS Exchange Server Programming |
Writing Simple Exchange Server Client
One topic that is worth researching is how can we write the simplest Exchange
server mail client program. How difficult is it to write a program that
sends an e-mail message via Exchange? This section investigates this in
detail. As usual, sample code (GAL/Client) is presented.
If we want to send an e-mail message, the following algorithm may be
used:
-
Open the OUT folder of a particular mailbox.
-
Create a message there.
-
Modify its properties as required and save the changes.
-
Prepare the recipients list.
-
Insert recipients list into the message.
-
Call the SubmitMessage method.
Some operations listed above are not trivial and require several steps.
I have compiled a program that sends a simple e-mail message to myself
via the Exchange server. Here is the code:
#include <afxwin.h>
#include <edk.h>
int main()
{
HRESULT hRes;
LPMAPISESSION pSession = NULL;
// Initialize MAPI
hRes = MAPIInitialize(NULL);
if (FAILED(hRes)) throw
-1;
// Obtain MAPI session
hRes = MAPILogonEx(0, // Handle
to parent window
"MS Exchange
Settings", // Profile name
NULL,
// Password
MAPI_NEW_SESSION
|
MAPI_EXTENDED
|
MAPI_LOGON_UI,
// Logon flags
&pSession);
// Resulting MAPI session
if (FAILED(hRes)) throw -1;
// Find default message store
ULONG cbDefStoreEid = 0;
LPENTRYID pDefStoreEid = NULL;
hRes = HrMAPIFindDefaultMsgStore(pSession, &cbDefStoreEid,
&pDefStoreEid);
if (FAILED(hRes)) throw -1;
// Open default message store
LPMDB pDefMsgStore = NULL;
hRes = pSession->OpenMsgStore(0, cbDefStoreEid,
pDefStoreEid, NULL,
MDB_WRITE | MAPI_DEFERRED_ERRORS,
&pDefMsgStore);
if (FAILED(hRes)) throw -1;
// Open user private information store
LPMDB pUserMsgStore = NULL;
hRes = HrMailboxLogon(pSession,
pDefMsgStore,
"/o=WRConsulting/ou=WhiteRock/cn=Configuration/cn=Servers/cn=MIG/cn=Microsoft
Private MDB",
"/o=WRConsulting/ou=WhiteRock/cn=Recipients/cn=NiKO",
// Mailbox DN
&pUserMsgStore);
if (FAILED(hRes)) throw -1;
// Open user message store root object
ULONG ulObjType = 0;
LPUNKNOWN pRoot = NULL;
hRes = pUserMsgStore->OpenEntry(0,
NULL,
NULL,
MAPI_BEST_ACCESS,
&ulObjType,
&pRoot);
if (FAILED(hRes)) throw -1;
// Obtain Entry ID for outbox.
ULONG ulValues;
LPSPropValue pPropValues = NULL;
hRes = pUserMsgStore->GetProps(NULL,
0,
&ulValues,
&pPropValues);
if (FAILED(hRes)) throw -1;
// Find PR_IPM_OUTBOX_ENTRYID
BOOL bOutboxFound = FALSE;
for (ULONG ul = 0; ul < ulValues; ul++)
{
if (PR_IPM_OUTBOX_ENTRYID
== pPropValues[ul].ulPropTag)
{
bOutboxFound
= TRUE;
break;
}
}
assert(bOutboxFound);
// We have PR_IPM_OUTBOX_ENTRYID here
// Try to open the Outbox
LPUNKNOWN pOutbox = NULL;
hRes = pUserMsgStore->OpenEntry(pPropValues[ul].Value.bin.cb,
(ENTRYID *) pPropValues[ul].Value.bin.lpb,
NULL,
MAPI_MODIFY, // MAPI_BEST_ACCESS,
&ulObjType,
&pOutbox);
if (FAILED(hRes)) throw -1;
// We have the Outbox opened...
// Create a message in there
LPMESSAGE pMsg = NULL;
hRes = ((IMAPIFolder *) pOutbox)->CreateMessage(NULL,
0, &pMsg);
if (FAILED(hRes)) throw -1;
// We need to modify message properties (introduce
a few others).
// Get properties.
ULONG ulVals;
LPSPropValue pPropVals = NULL;
hRes = pMsg->GetProps(NULL,
0,
&ulVals,
&pPropVals);
if (FAILED(hRes)) throw -1;
// Allocate another (bigger) SPropValue array
SPropValue * pNewProps = new SPropValue[ulVals +
3];
assert(pNewProps);
// Copy original data in
for (ul = 0; ul < ulVals; ul++)
{
pNewProps[ul].ulPropTag
= pPropVals[ul].ulPropTag;
pNewProps[ul].Value.bin
= pPropVals[ul].Value.bin;
}
// Introduce subject
pNewProps[ulVals].ulPropTag = PR_SUBJECT;
pNewProps[ulVals++].Value.lpszA = "My subject";
// Introduce body
pNewProps[ulVals].ulPropTag = PR_BODY;
pNewProps[ulVals++].Value.lpszA = "My body";
// Introduce PR_DELETE_AFTER_SUBMIT
// This is needed, otherwise a copy stays in the
Outbox
pNewProps[ulVals].ulPropTag = PR_DELETE_AFTER_SUBMIT;
pNewProps[ulVals++].Value.b = TRUE;
// Set props
hRes = pMsg->SetProps(ulVals,
pNewProps,
NULL);
if (FAILED(hRes)) throw -1;
// Save changes
hRes = pMsg->SaveChanges(KEEP_OPEN_READWRITE);
if (FAILED(hRes)) throw -1;
// Prepare recipients list...
ADRLIST * pal = NULL;
MAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID *) &pal);
pal->cEntries = 1;
SPropValue * pPropArray = NULL;
MAPIAllocateBuffer(3 * sizeof(SPropValue), (LPVOID*)
&pPropArray);
pPropArray[0].ulPropTag = PR_RECIPIENT_TYPE;
pPropArray[0].Value.l = MAPI_TO;
pPropArray[1].ulPropTag = PR_DISPLAY_NAME;
pPropArray[1].Value.lpszA = "Nik Okuntseff";
pPropArray[2].ulPropTag = PR_ADDRTYPE;
pPropArray[2].Value.lpszA = "EX"; // Exchange
address type
pal->aEntries[0].ulReserved1 = 0;
pal->aEntries[0].cValues = 3; // This
"3" is important, otherwise the ResolveName fails!
pal->aEntries[0].rgPropVals = pPropArray;
// We'll IAddrBook interface to resolve display names
to entry IDs.
LPADRBOOK pAdrBook = NULL;
hRes = pSession->OpenAddressBook(0, 0, MAPI_ACCESS_MODIFY,
&pAdrBook );
if (FAILED(hRes)) throw -1;
// Call IAdrBook::ResolveName method to resolve
display names in our list to entry IDs.
hRes = pAdrBook->ResolveName(0, 0, NULL, pal);
if (FAILED(hRes)) throw -1;
// Insert our recipients list into the message
hRes = pMsg->ModifyRecipients(0, pal);
if (FAILED(hRes)) throw -1;
// Submit
hRes = pMsg->SubmitMessage(0);
if (FAILED(hRes)) throw -1;
// Log off from user mailbox
hRes = HrMailboxLogoff(&pUserMsgStore);
if (FAILED(hRes)) throw -1;
// Clean up and exit
if (pAdrBook)
pAdrBook->Release();
if (pPropArray)
MAPIFreeBuffer(pPropArray);
if (pal)
MAPIFreeBuffer(pal);
if (pNewProps)
MAPIFreeBuffer(pNewProps);
if (pPropVals)
MAPIFreeBuffer(pPropVals);
if (pMsg)
pMsg->Release();
if (pOutbox)
pOutbox->Release();
if (pPropValues)
MAPIFreeBuffer(pPropValues);
if (pRoot)
pRoot->Release();
if (pUserMsgStore)
pUserMsgStore->Release();
if (pDefMsgStore)
pDefMsgStore->Release();
if (pDefStoreEid)
MAPIFreeBuffer(pDefStoreEid);
if (pSession)
pSession->Release();
::MAPIUninitialize();
return 0;
}
Let me go through this code and explain what happens. I initialize MAPI
and obtain a MAPI session with the MAPILogonEx call. We need a MAPI session
to open other objects. Then I need access to the OUT folder of someone's
mailbox. The code above will work with any mailbox if you have necessary
administrative access to Exchange server. Before you can open the OUT folder
you need access to the mailbox itself. This is accomplished with the HrMailboxLogon
call. It returns a pointer to the user's private information store.
The code above uses privileged access to private information stores.
This means it can access anyone's mail. In fact, your security context
should have enough rights to achieve this (otherwise the HrMailboxLogon
fails). Server programs (gateways, mailbox agents and the like) often use
this technique. Alternatively, you can open your own private message store
with less powerful account (not necessarily using the HrMailboxLogon) and
use it to send/receive mail for your own mailbox.
After logging on to the mailbox (my own mailbox in this case) I locate
the Outbox and open it. Then I create a new message there and introduce
three new properties for it: PR_SUBJECT, PR_BODY and PR_DELETE_AFTER_SUBMIT.
I save the changes into the message object. Notice the use of KEEP_OPEN_READWRITE
flag in the SaveChanges method. I need the message object open. If I were
not using this flag the subsequent SubmitMessage call would fail.
Next step is to prepare recipients list. This is done in 2 steps. First,
the recipients list is initialized with one entry containing the display
name of one recipient (myself). Second, the list is resolved by using the
IAddrBook::ResolveName method. This methods obtains entry IDs for recipients.
After that the recipients list is ready to be inserted into the message
with the IMessage::ModifyRecipients call.
Finally I submit the message to Exchange server for processing. The
code is written for my own MS Exchange installation and assumes certain
things (site names, DNs, etc.). You'll need to change them accordingly.
The code illustrates how a simple practical thing like sending a simple
e-mail message may be accomplished in Exchange server environment. As you
could see, the procedures are not trivial sometimes. A lot of things need
to be taken care of. This code, as all other samples in this book does
not care about error processing. If you add proper error processing for
robustness, the issue becomes larger and more difficult.
[ Contents |
Home
]
Send comments and suggestions to niko@wrconsulting.com
Copyright © 1997-1998 by Nik Okuntseff
|