15 September 2006 2:25
First off, I have cross-posted this to microsoft.public.exchange.development and the Exchange Server->3rd party applications forum. I've also posted this once before, but I can't find it now--it appears to be gone. If this is a duplicate, my apologies. Reply to email@example.com, or within the forum, please.
In essence, I am writing an OnMailTransportCategorize sink. When an Outlook user sends email (Plain Text format, btw) to another user on the same server (or elsewhere in the domain), I can intercept the email, but the body and attachments are blank--there's just winmail.dat.
Now, I'm aware of this problem: If you're sending via MAPI, then what gets sent through the sink is an SMTP copy of the MAPI message. According to Microsoft, you can't modify this message, and the attachments and body may be blank: http://support.microsoft.com/default.aspx?scid=kb;en-us;273233
Microsoft's solution is to force the messages through a border server or a smart host, and put the sink on that host. However, I need to get the body and attachments when a user is sending to another user on the same server! What do I do? The body/attachments are there when the message is sent; they're there when the message arrives; they must be available SOMEWHERE.
Now, a couple of solutions do present themselves. First, I could try to hook the server event OnSyncSave, as the sent message is copied to the Sent Items folder. This has obvious problems: 1) I've read somewhere (not sure) that this won't work; 2) I don't know if this would solve the problem (the message might get sent anyway); 3) the user might not have his messages copied to Sent Messages.
Second, if the body is, in fact, somewhere in winmail.dat, I could try to extract it from TNEF format. The MAPI function OpenTnefStream() seems applicable here, but it seems (so far as I can tell) to require a profile. (See http://windowssdk.msdn.microsoft.com/en-us/library/ms527086.aspx.) This is, of course, on the server itself--it doesn't seem to have profiles, and I get error 0x80040700 (CdoE_AMBIGUOUS_RECIP) when I try to create one. Is there a way to access this function without creating/logging onto a profile? Or should I be able to create a profile on the Exchange server?
Third, I could try to parse the winmail.dat myself. However, Microsoft's TNEF stream description has some severe syntax errors, so far as I can tell. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/mapi/html/ca148ec3-8586-4c74-8ff8-cd542256e385.asp -- the syntax is given without separators of any kind--for example, the Object syntax expands to Message_Seq Message_Seq Attach_Seq Attach_Seq, when it's clearly supposed to be something like Message_Seq | Message_Seq Attach_Seq | Attach_Seq. (See the description within the article itself.) So, I'm having trouble extracting the pieces.
Finally, I saved the winmail.dat that my transport sink saw to a file and opened it in binary mode. Some, but not all, of my body was there--I sent a message with a body of "1234567890" and saw in the binary format something like "..23. .45678..0" that was clearly part of my body. (The periods represent non-printable characters. I checked this on other bodies as well.) So if the body is in winmail.dat, it's garbled or coded somehow. When I look at the winmail.dat using an open-source TNEF parser, it shows as empty--no attachments at all.
16 September 2006 6:58
I asume this question is about Exchange 2003. You can try something along the following line:
1. Initialize MAPI with MAPIInitialize(NULL)
2. Initialize OLE with OleInitialize(NULL)
3. Create new OLE storage, for example with StgCreateStorageEx(..STGFMT_STORAGE..). For efficiency you may want to create storage in-memory, look for StgCreateDocfileOnILockBytes, CreateILockBytesOnHGlobal.
4. Open IMessage on your new storage with OpenIMsgOnIStg. Pass NULL as lpMsgSess.
5. Open IStream on TNEF content (hopefully you already know how to parse MIME and decode TNEF attachment - winmail.dat - if it was base64-encoded).
6. Call OpenTnefStreamEx with TNEF_DECODE, pass in TNEF IStream and IMessage you opened on IStorage.
7. Call lpTNEF->ExtractProps to extract all properties from TNEF stream into your IMessage.
8. Now use IMessage as if it was a normal MAPI message (you need to learn MAPI). You can call GetPropList, GetProps, OpenProperty, GetAttachmentTable and so on. Message body can be one of PR_BODY, PR_BODY_HTML or PR_RTF_COMPRESSED. For attachment content, look for PR_ATTACH_DATA_BIN. There can be much more here – e.g. PR_BODY and PR_HTML can be either STRING8 or UNICODE, PR_RTF_COMPRESSED is a compressed RTF which you may need to uncompress with WrapCompressedRTFStream, original text can be hidden inside RTF body, attachments can be OLE-type and on and on. Welcome to MAPI world.
9. Don't forget to check return code from each method you call.
10. Don’t forget to clean up – release all objects and free memory.
This way I think you can get away without MAPI profile and MAPI session. Hope this helps.
Good news is that in Exchange 2007 this is completely supported and an order of magnitude simpler. Check new managed transport API.
This posting is provided "AS IS" with no warranties, and confers no rights.
21 September 2006 15:09
Thanks very much! I haven't had time to try it yet--I've been pulled onto another project. I'll reply when I know if it worked. I appreciate your time.