Monday, October 01, 2007

Processing embedded attachments in a Transport Agent

Continuing on from my other post last week this is a little bit more of an advanced Transport agent that can process attachments within embedded messages down to any depth. Most of the code is the same as last week except that the structure of the code has been changed so each message and embedded message is processed by the routine. To see if a attachment has an assoiciated embeeded message all you need to do is check the EmbeddedMessage property on each message. The one thing that I found interesting is when processing an embedded message vs. just processing the message that caused the Agent to fire is that there is no underlying MIME document so the code I had that was saving out the message to a separate directory using the MIME document in this case wouldn’t work for an embedded message. As I was really only interested in processing attachments this was not so much of a problem but its one thing to be careful of if you do what to processes embedded messages. For details of what this code does please read last weeks post.

I’ve put a download of the code here the code itself looks like

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Mime;
using Microsoft.Exchange.Data.Transport.Email;
using Microsoft.Exchange.Data.Transport.Smtp;
using Microsoft.Exchange.Data.Transport.Routing;
using Microsoft.Exchange.Data.Common;

namespace msgdevExchangeRoutingAgents
{
public class EmailArchivingFactory : RoutingAgentFactory
{
public override RoutingAgent CreateAgent(SmtpServer server)
{
RoutingAgent emMailArchive = new EmailArchivingRoutingAgent();
return emMailArchive;
}
}
}

public class EmailArchivingRoutingAgent : RoutingAgent
{
public EmailArchivingRoutingAgent()
{
base.OnSubmittedMessage += new SubmittedMessageEventHandler(EmailArchivingRoutingAgent_OnSubmittedMessage);
}

void EmailArchivingRoutingAgent_OnSubmittedMessage(SubmittedMessageEventSource source, QueuedMessageEventArgs e)
{
EmailMessage emMessage = e.MailItem.Message;
String MessageGuid = Guid.NewGuid().ToString();
ProcessMessage(emMessage, MessageGuid);

}
public static byte[] ReadFully(Stream stream, int initialLength)
{
// ref Function from http://www.yoda.arachsys.com/csharp/readbinary.html
// If we've been passed an unhelpful initial length, just
// use 32K.
if (initialLength < initiallength =" 32768;" buffer =" new" read =" 0;" chunk =" stream.Read(buffer,"> 0)
{
read += chunk;

// If we've reached the end of our buffer, check to see if there's
// any more information
if (read == buffer.Length)
{
int nextByte = stream.ReadByte();

// End of stream? If so, we're done
if (nextByte == -1)
{
return buffer;
}

// Nope. Resize the buffer, put in the byte we've just
// read, and continue
byte[] newBuffer = new byte[buffer.Length * 2];
Array.Copy(buffer, newBuffer, buffer.Length);
newBuffer[read] = (byte)nextByte;
buffer = newBuffer;
read++;
}
}
// Buffer is now too big. Shrink it.
byte[] ret = new byte[read];
Array.Copy(buffer, ret, read);
return ret;
}
static void ProcessMessage(EmailMessage emEmailMessage, String MessageGuid)
{
//Archive Message


if (emEmailMessage.MimeDocument != null)
{

Stream fsFileStream = new FileStream(@"C:\temp\archive\messages\" + MessageGuid + ".eml", FileMode.OpenOrCreate);
emEmailMessage.MimeDocument.WriteTo(fsFileStream);
fsFileStream.Close();
}
//Archive Any Attachments Check for pdf attachments under 20 K and delete
ArrayList adAttachmenttoDelete = new ArrayList();
for (int index = emEmailMessage.Attachments.Count - 1; index >= 0; index--)
{
Attachment atAttach = emEmailMessage.Attachments[index];
if (atAttach.EmbeddedMessage != null)
{
EmailMessage ebmMessage = atAttach.EmbeddedMessage;
ProcessMessage(ebmMessage, MessageGuid);
}
else
{
if (atAttach.AttachmentType == AttachmentType.Regular & atAttach.FileName != null)
{
FileStream atFileStream = File.Create(Path.Combine(@"C:\temp\archive\attachments\", MessageGuid + "-" + atAttach.FileName));
Stream attachstream = atAttach.GetContentReadStream();
byte[] bytes = ReadFully(attachstream, (int)attachstream.Length);
atFileStream.Write(bytes, 0, bytes.Length);
atFileStream.Close();
atFileStream = null;
bytes = null;
// Find Any PDF attachments less then 20 KB
if (atAttach.FileName.Length >= 3)
{
String feFileExtension = atAttach.FileName.Substring((atAttach.FileName.Length - 4), 4);
if (feFileExtension.ToLower() == ".pdf" & attachstream.Length < 20480)
{
adAttachmenttoDelete.Add(atAttach);
}

}
attachstream.Close();
attachstream = null;
}
atAttach = null;
}
}
//Delete Attachments
if (adAttachmenttoDelete.Count != 0)
{
IEnumerator Enumerator = adAttachmenttoDelete.GetEnumerator();
while (Enumerator.MoveNext())
{
emEmailMessage.Attachments.Remove((Attachment)Enumerator.Current);
}
}

}
}

18 comments:

Dunkin' said...

Glen,

The Attachment's EmbeddedMessage property returns an EmailMessage object. Pass the EmailMessage object's MimeDocument property to your method.

Right now I'm trying to figure out how to create an attachment that contains contains an embedded message. If you figure this out please post it.

Anonymous said...

Hi..

I'd like to implement an exchange extension / plugin, it should be a kind of antispam plugin but I'm not sure how to start with exchange programming.. Do you know good books on this topic ?

thanks in advance

Reinhard Wagner

Anonymous said...

Hi Glen,

have you ever found out how an embedded message can be saved to disk? As you mentionned, the MimeDocument property of an embedded message is null. I can't see how I could convert the embedded message to something which as the possibilty to be saved to disk or at least read as stream.

Mathis Marugg

Glen said...

If you have a look at the first comment someone has put an answer for you. Basically you can get the MIME document of the embeeded message which you can the pass back into the MIME parser and unwind it from there.

Cheers
Glen

Anonymous said...

Thanks for your replay. Unfortunately the first comment is not 100% correct. If an attachment's EmbeddedMessage property is set, the returned EmailMessage object contains everything except the MimeDocument. This property is always null for embedded messages.

@ Dunkin': Generating an attachment that contains an embedded message is easily done within Outlook 2007 client. Just insert an existing mail to a new mail an tell it to be inserted as an attachment.

Glen said...

I dont think thats what he meant and its not what i meant either. Basically you can take the Mime document of the EmbeddedMessage and then reparse it into another Mime document. All the attachments and related attachments will be part of the Mime doucment so can be parsed out. The restrictions is around the depth the Mime parser will go for embeeded messages so this is why you need some logic to unwind it using a depth of 1.

Cheers
Glen

Anonymous said...

Hi Glen

I spent another few hours on that, but was not able to get to a correct MimeDocument. I fail in getting a stream out of the embedded message representing the mime document.

So I ask myself (and the community on TechNet http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/fcb519e5-cb9d-48e2-9316-046500f13c04) if anyone has an idea why we have this behaviour and if there is a workaround.

Glen said...

You wont get anything out of the embeeded message property what you need to do is get the Outer message's Mime stream then loop through the MIME parts find the embedded message then decoded it then you should be able to reload it into the MIME parser (or another MIME parser like CDOEX).

Cheers
Glen

Anonymous said...

Hey Glen! Great Script, thank you so much:)!
Now I'm trying hard to get it into my Exchange2007 HubRole Server.
Using Powershell, it ask me for "TransportAgentFactory". Could you please help me out what this in your Case means?
Thanks in advance!

Glen said...

For this agent the Transport factory name is msgdevExchangeRoutingAgents.EmailArchivingFactory

this is a combination of the Namespace and class name.

Cheers
Glen

Anonymous said...

Hey Glen!
Thank you again:)! It's working properly!
But I got one more question, what if I only want to use that script for one particular emailadress? e.g. You would like to Filter all Excel-Attachments out of info@blahblah.com ?
Thanks again and wish you a nice day!

Glen said...

You need to add some logic in the code to check the sender address. Eg with the transport agent class you have access to both p1 and p2 recipeint headers and from address. You should put the logic at the beginning of your code and get it to skip the rest if the address doesn't match.

Cheers
Glen

Anonymous said...

Hey Glen,
thanks for the fast answer.
Does p1 and p2 work with rcpt addresses too? like somebody sends an email with excel attachment to info@..

Glen said...

these are the recipient headers one is the Envelope headers which would contain who the mail is actually going to (eg if someone BCC the message then this would be the only place you would see the address) the other would contain the actual message's TO and CC address that a user would see.

Cheers
Glen

Anonymous said...

Hey Glen,
I just don't get familiar with this agent-stuff..
Is there no Tutorial for RealDummies which explains how to do simple things like "What kinda commands are in SmtpReceiveAgent" or "How do I have to start to code" and so on. All of those "Simple Agent Samples" are so simple that it's to difficult for me..

Glen said...

Not that i know of I've certainly suggested that to MS but Im just one guy and what i say doesn't really hold much water. On the MSDN page there is a submitt feedback link try using that link to ask MS for better sample/information.

cheers
Glen

Karen said...

Hi,
I am attempting to create a forwarded message From within the
OnSubmitMessageHandler (Agent) utilizing the Attachment.EmbeddedMessage
property of an existing message.

I add an attachment to the existing message.

When I attempt to Set the EmbeddedMessage property of the newly created
attachment to a newly created EmailMessage (Created via EmailMessage.Create())

I encounter an error stating that the attachment is not an embedded message.
Is there a way to solve it, any info is appreciated.

Anonymous said...

Hi,

I have the same error when try to set EmbeddedMessage property - "Cannot set the property, the attachment is not an embedded message."
I get the same message even when add attachment as "message/rfc822".

Can you please advise how to add embedded email?

Thanks,
Alex