none
[E2007][TA][C#]: Modify Body of Message Using Transport Agent RRS feed

  • Question

  • Hello,

    I've been tasked with creating a custom transport agent for our Exchange 2007 environment. Basically in a nutshell I need to cut the first 7 lines out of the body of an email. The agent will only fire on certain emails that meet criteria I need to specify, basically the From and To addresses.

    I've been researching it but haven't found much so far, if anyone can give any assistance/guidance/snippets of code it's really appreciated. Thanks.

    Monday, August 8, 2011 6:55 PM

All replies

  • I've found some info, namely http://blogs.msdn.com/b/mstehle/archive/2009/01/13/howto-sample-transport-agent-add-headers-categories-mapi-props-even-uses-a-fork.aspx, which gives something to start with although it does a lot more than what I need. Any help is much appreciated.
    Monday, August 8, 2011 7:22 PM
  • Here is what I have so far, but at the moment I'm stuck on how to remove what I need:

     

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Exchange.Data.Transport;
    using Microsoft.Exchange.Data.Transport.Smtp;
    
    namespace MyAgents
    {
      public sealed class MyAgentFactory : SmtpReceiveAgentFactory
      {
        public override SmtpReceiveAgent CreateAgent(SmtpServer server)
        {
          return new MyAgent();
        }
      }
      public class MyAgent : SmtpReceiveAgent
      {
        public MyAgent()
        {
          this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
        }
        private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)
        {
          // The following line appends text to the subject of the message that caused the event.
          if ((e.MailItem.Message.From.Equals("support@ignitionmsp.com")) && (e.MailItem.Recipients.Contains("flagstonere")))
          {
            Microsoft.Exchange.Data.Transport.Email.Body msgBody = e.MailItem.Message.Body;
            string charsetName;
            charsetName = msgBody.CharsetName;
            System.IO.Stream msgBodyStream = msgBody.GetContentReadStream(charsetName);
            System.IO.Stream myBodyWriteStream = msgBody.GetContentWriteStream(charsetName);
            byte[] buffer = new byte[msgBodyStream.Length];
            int read = 0;
            int chunk;
            while ((chunk = myBodyReadStream.Read(buffer, read, buffer.Length - read)) > 0)
            {
              read += chunk;
            }
            System.IO.Stream myBodyWriteStream;
            msgBodyWriteStream.Write(buffer, 0, buffer.Length);
            myBodyWriteStream.Flush();
          }
        }
      }
    }
    


    Tuesday, August 9, 2011 5:12 PM
  • I'd suggest you check the Body Conversion sample which goes through the specifics of what you need to do to deal with messages that are in different formats http://msdn.microsoft.com/en-us/library/bb204066(v=EXCHG.140).aspx

    Cheers
    Glen

    Wednesday, August 10, 2011 6:45 AM
  • Thanks Glen, I looked at the example, and used it as a skeleton to add in what I want to do. I can add a header/footer easily, as that's what the example does, but I'm having trouble getting to the actual body of the email, parsing it and cutting out I what I want to. Any suggestions?

     

    namespace Microsoft.Exchange.Samples.Agents.BodyConversion
    {
      using System;
      using System.IO;
      using System.Text;
      using System.Diagnostics;
      using Microsoft.Exchange.Data.Transport;
      using Microsoft.Exchange.Data.Transport.Smtp;
      using Microsoft.Exchange.Data.Transport.Email;
      using Microsoft.Exchange.Data.TextConverters;
    
      /// <summary>
      /// Agent factory
      /// </summary>
      public class BodyConversionFactory : SmtpReceiveAgentFactory
      {
        /// <summary>
        /// Create a new BodyConversion
        /// </summary>
        /// <param name="server">Exchange server</param>
        /// <returns>A new BodyConversion</returns>
        public override SmtpReceiveAgent CreateAgent(SmtpServer server)
        {
          return new BodyConversion();
        }
      }
    
      /// <summary>
      /// SmtpReceiveAgent for the BodyConversion sample.
      /// </summary>
      public class BodyConversion : SmtpReceiveAgent
      {
        /// <summary>
        /// An object to synchronize access to the log file.
        /// </summary>
        private object fileLock = new object();
    
    
        /// <summary>
        /// The constructor registers an end of data event handler.
        /// </summary>
        public BodyConversion()
        {
          Debug.WriteLine("[BodyConversion] Agent constructor");
          this.OnEndOfData += new EndOfDataEventHandler(this.OnEndOfDataHandler);
        }
    
        /// <summary>
        /// 
        /// Invoked by Exchange when the entire message has been received.
        /// </summary>
        /// <param name="source">The source of this event.</param>
        /// <param name="eodArgs">The arguments for this event.</param>
        public void OnEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs eodArgs)
        {
    
          Debug.WriteLine("[BodyConversion] OnEndOfDataHandler is called");
    
          // The purpose of this sample is to demonstrate how TextConverters can be used
          // to update the body. Let's imagine that we need to ensure that no active
          // content, such as scripts, can pass through in a message body.
    
          EmailMessage message = eodArgs.MailItem.Message;
          Stream originalBodyContent = null;
          Stream newBodyContent = null;
          Encoding encoding;
          string charsetName;
    
          try
          {
    
            Body body = message.Body;
            BodyFormat bodyFormat = body.BodyFormat;
    
            if (!body.TryGetContentReadStream(out originalBodyContent))
            {
              // we cannot decode the body
              return;
            }
    
            if (BodyFormat.Text == bodyFormat)
            {
              // plain text body, let's simply add a disclaimer to this body for 
              // demonstration purposes
              if ((message.From.SmtpAddress == "Ignition@mailflow.ignition.bm") && (eodArgs.MailItem.Recipients[0].Address.ToString() == "darrin.henshaw@gmail.com"))
              {
                eodArgs.MailItem.Message.Subject = eodArgs.MailItem.Message.Subject.Replace("- Ignition Detected / an Alert INM Alarm", null);
                charsetName = body.CharsetName;
                if (null == charsetName ||
                  !Microsoft.Exchange.Data.Globalization.Charset.TryGetEncoding(charsetName, out encoding))
                {
                  // either no charset, or charset is not supported by the system
                  return;
                }
    
                // create and set up the TextToText conversion object
                TextToText textToTextConversion = new TextToText();
    
                // don't change the body codepage
                textToTextConversion.InputEncoding = encoding;
                // note: by default the output codepage is the same as input codepage
    
                // let's add a disclaimer to indicate that the body is not being filtered
                textToTextConversion.HeaderFooterFormat = HeaderFooterFormat.Text;
                textToTextConversion.Header = "the plain text body is NOT being filtered";
    
                // open the stream to write updated content into
                newBodyContent = body.GetContentWriteStream();
    
                // now let's do the conversion. This will replace the original text
                // with an updated version.
                try
                {
                  // the easiest way to do the conversion in one step is using the Convert 
                  // method. It takes care for creating appropriate converter stream
                  // and copying from one stream to another.
                  textToTextConversion.Convert(originalBodyContent, newBodyContent);
                }
                catch (Microsoft.Exchange.Data.TextConverters.TextConvertersException)
                {
                  // the conversion has failed..
                }
              }
            }
            else if (BodyFormat.Html == bodyFormat)
            {
              // the HTML body 
    
              // let's filter the original HTML to remove any scripts and other
              // potentially unsafe content.
              if ((message.From.SmtpAddress == "Ignition@mailflow.ignition.bm") && (eodArgs.MailItem.Recipients[0].Address.ToString() == "darrin.henshaw@gmail.com"))
              {
                eodArgs.MailItem.Message.Subject = eodArgs.MailItem.Message.Subject.Replace("- Ignition Detected / an Alert INM Alarm", null);
    
                charsetName = body.CharsetName;
                if (null == charsetName ||
                  !Microsoft.Exchange.Data.Globalization.Charset.TryGetEncoding(charsetName, out encoding))
                {
                  // either no charset, or charset is not supported by the system
                  return;
                }
                Microsoft.Exchange.Data.TextConverters.
                // create and set up the HtmlToHtml conversion object
                HtmlToHtml htmlToHtmlConversion = new HtmlToHtml();
                
                // don't change the body codepage
                htmlToHtmlConversion.InputEncoding = encoding;
                // note: by default the output codepage is the same as input codepage
                TextToHtml htmlToText = new TextToHtml();
               
                // setup output HTML filtering. Html filtering will ensure that
                // no scripts or active content can pass through.
                htmlToHtmlConversion.FilterHtml = true;
    
                // let's add a disclaimer to indicate that the body is being filtered
                htmlToHtmlConversion.HeaderFooterFormat = HeaderFooterFormat.Html;
                htmlToHtmlConversion.Header = "<p><font face=\"Arial\" size=1>the HTML body is being filtered</font></p>";
    
                // open the stream to write updated content into
                newBodyContent = body.GetContentWriteStream();
    
                // now let's do the conversion. This will replace the original HTML
                // with a filtered version.
                try
                {
                  // the easiest way to do the conversion in ocne step is using the Convert 
                  // method. It takes care for creating appropriate converter stream
                  // and copying from one stream to another.
                  htmlToHtmlConversion.Convert(originalBodyContent, newBodyContent);
                }
                catch (Microsoft.Exchange.Data.TextConverters.TextConvertersException)
                {
                  // the conversion has failed..
                }
              }
            }
            else if (BodyFormat.Rtf == bodyFormat)
            {
              if ((message.From.SmtpAddress == "Ignition@mailflow.ignition.bm") && (eodArgs.MailItem.Recipients[0].Address.ToString() == "darrin.henshaw@gmail.com"))
              {
                eodArgs.MailItem.Message.Subject = eodArgs.MailItem.Message.Subject.Replace("- Ignition Detected / an Alert INM Alarm", null);
                // this is compressed RTF body in a TNEF message.
    
                // Compressed RTF body can contain the encapsulated original HTML and we 
                // want to filter it.
    
                // We don't want to modify the message format here, we just want to filter out 
                // potentially unsafe content. Thus we are converting RTF to HTML, filtering any
                // unsafe HTML content and converting the result back to RTF.
    
                // Note that conversion from RTF to HTML and back to RTF can be lossy. Some
                // embedded images in RTF can be lost and formatting can slightly change.
    
                // first wrap the original body content, which is in a "compressed RTF" format, into
                // a Stream which will do RTF decompression for us. Reading from this stream
                // will return original RTF (the same format which Word and RichEdit control are using)
                ConverterStream uncompressedRtf = new ConverterStream(originalBodyContent, new RtfCompressedToRtf(), ConverterStreamAccess.Read);
    
                // create and configure the RtfToHtml conversion object
                RtfToHtml rtfToHtmlConversion = new RtfToHtml();
    
                // setup output HTML filtering. Html filtering will ensure that
                // no scripts or active content can pass through.
                rtfToHtmlConversion.FilterHtml = true;
    
                // let's add a disclaimer to indicate that the body is being filtered
                rtfToHtmlConversion.HeaderFooterFormat = HeaderFooterFormat.Html;
                rtfToHtmlConversion.Header = "<p><font face=\"Arial\" size=1>the RTF body is being filtered</font></p>";
    
                // now create wrapping TextReader object, which returns the filtered 
                // HTML converted (or extracted) from the original RTF.
                ConverterReader html = new ConverterReader(uncompressedRtf, rtfToHtmlConversion);
    
                // we have a filtered HTML, let's convert it back to RTF. We don't want
                // to change the message format for the purposes of this sample.
    
                // Create and configure the HtmlToRtf conversion object
                HtmlToRtf htmlToRtfConversion = new HtmlToRtf();
    
                // create a wrapping stream wich returns RTF converted back from filtered HTML
                ConverterStream filteredRtf = new ConverterStream(html, htmlToRtfConversion);
    
                // Create and configure the RtfToRtfCompressed conversion object, which
                // will compress the resulting RTF.
                RtfToRtfCompressed rtfCompressionConversion = new RtfToRtfCompressed();
    
                // let's always write it back in a compressed form
                rtfCompressionConversion.CompressionMode = RtfCompressionMode.Compressed;
    
                // open the stream to write updated body content into
                newBodyContent = body.GetContentWriteStream();
    
                // finally let's do the complete chain of conversions we set up above. This 
                // will read the original compressed RTF chunk by chunk, convert it to HTML 
                // and filter scripts, than convert HTML back to RTF, compress it and write 
                // back into the current message body.
                try
                {
                  // Use Convert method because it is convinient. Effectively what this Convert
                  // method does is compressing the resulting RTF it reads from the conversion 
                  // chain stream we set up above and writes the result into the output stream. 
                  // It saves us a few lines of code - e.g. creating another wrapper stream and 
                  // copying from one stream to another.
                  rtfCompressionConversion.Convert(filteredRtf, newBodyContent);
                }
                catch (Microsoft.Exchange.Data.TextConverters.TextConvertersException)
                {
                  // the conversion has failed..
                }
              }
            }
    
            else
            {
              // Handle cases where the body format is not one of the above.
            }
          }
    
          finally
          {
            if (originalBodyContent != null)
            {
              originalBodyContent.Close();
            }
    
            if (newBodyContent != null)
            {
              newBodyContent.Close();
            }
          }
        }
      }
    }
    
    


    Friday, August 12, 2011 12:23 PM
  • What type of messages are you actually dealing with when i say this i mean is the message your processing in TNEF format or is it a MIME message. You should be able to just use the converters (using the underlying streams) to do this but its going to vary depending on what type of message body you have eg text,html or RTF (if its TNEF).

    Cheers
    Glen

     

    Tuesday, August 16, 2011 12:37 PM
  • What type of messages are you actually dealing with when i say this i mean is the message your processing in TNEF format or is it a MIME message. You should be able to just use the converters (using the underlying streams) to do this but its going to vary depending on what type of message body you have eg text,html or RTF (if its TNEF).

    Cheers
    Glen

     

    Hi Glen,

    Can you please tell why I cannot get internal read stream of Text Emails:

     Body body = message.Body;
     BodyFormat bodyFormat = body.BodyFormat;
    //following line fails if i get Text email message
    if (!body.TryGetContentReadStream(out riginalBodyContent))        {
              // we cannot decode the bodyreturn        
    }

    This line

    if (!body.TryGetContentReadStream(out originalBodyContent))

    always fail when working with Text emails, and giving success when working with HTML emails.

    Thanks in anticipation.


    Regards, David Johnson


    Wednesday, March 7, 2012 1:12 AM
  • If they are Internal messages they generally wont be in a MIME Text format you will have a TNEF Stream and the Body of the Message (no matter the format) will be in one of the TNEF properties.

    Eg check to see if there is a TNEFPart in  MailItem.Message.TnefPart

    The other thing would be to try GetRawContentStream http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.mime.mimereader.getrawcontentreadstream%28v=exchg.140%29.aspx

    Cheers
    Glen

    Wednesday, March 7, 2012 1:55 AM
  • If they are Internal messages they generally wont be in a MIME Text format you will have a TNEF Stream and the Body of the Message (no matter the format) will be in one of the TNEF properties.

    Eg check to see if there is a TNEFPart in  MailItem.Message.TnefPart

    The other thing would be to try GetRawContentStream http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.mime.mimereader.getrawcontentreadstream%28v=exchg.140%29.aspx

    Cheers
    Glen

    Hi Glen,

    I would test it today, and would update this thread.

    Thank you very much for your input, I really appreciate it.


    Regards, David Johnson

    Wednesday, March 7, 2012 1:58 AM
  • If they are Internal messages they generally wont be in a MIME Text format you will have a TNEF Stream and the Body of the Message (no matter the format) will be in one of the TNEF properties.

    Eg check to see if there is a TNEFPart in  MailItem.Message.TnefPart

    The other thing would be to try GetRawContentStream http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.mime.mimereader.getrawcontentreadstream%28v=exchg.140%29.aspx

    Cheers
    Glen

      Hi Glen,

     I tried reading the stream from TnefPart, and succeeded in getting stream, but that stream doesn't represent body of the email message, as I can see other data (possilbly email header data) and non-Ascii characters too in stream, if i read the stream into bytes and then convert to string using .net encoding class.

    I tried:

    MimePart tnef= args.MailItem.Message.TnefPart;
    tnef.TryGetContentReadStream(out myBodyReadStream);
    
    and also this:
    
    MimePart tnef= args.MailItem.Message.TnefPart;
    myBodyReadStream = tnef.GetRawContentReadStream();

    Above both ways doesn't fail, but also doesn't provide Message body stream. 

    Also this line:

    myBodyReadStream = args.MailItem.GetMimeReadStream();

    returns me a better stream, bcoz when I convert it into string, then I can see more readable lines of data than the above ones, which i got using tnef.

    Can you please help me in this regard further?

    Please Note: type of args is QueuedMessageEventArgs;

    Thanks in anticipation.


    Regards, David Johnson

    Sunday, March 11, 2012 11:56 PM
  • If the message is in TNEF format than thats what you have and what you have to deal with there is no RFC MimeSteam

    > I tried reading the stream from TnefPart, and succeeded in getting stream, but that stream doesn't represent body of the email message

    The TNEFStream is a serialized transportable binary stream of the all the Mapi properties that represent a message you can use the TNEFReader to read the Body property via TnefAttributeTag.Body. And if you going to change this you will need to use the TNEFWritter to write changes to the stream. See http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/7015cfc6-9345-460d-a14b-83d42fc1e9e7

    Cheers
    Glen

    Monday, March 12, 2012 4:15 AM
  • Hi Glen,

    I would work on it in next 2 days and would update this thread.

    Thank you again for this help.


    Regards, David Johnson

    Monday, March 12, 2012 1:17 PM
  • If the message is in TNEF format than thats what you have and what you have to deal with there is no RFC MimeSteam

    > I tried reading the stream from TnefPart, and succeeded in getting stream, but that stream doesn't represent body of the email message

    The TNEFStream is a serialized transportable binary stream of the all the Mapi properties that represent a message you can use the TNEFReader to read the Body property via TnefAttributeTag.Body. And if you going to change this you will need to use the TNEFWritter to write changes to the stream. See http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/7015cfc6-9345-460d-a14b-83d42fc1e9e7

    Cheers
    Glen

    Hi Glen,

    I am now able to modify Text email body too using TnefReader and TnefWrite, though just having a little problem, due to which sometimes email arrives in user's inbox with no body. 

    I would post complete details in couple of days, it seems that I have to end it very soon.


    Regards, David Johnson

    Saturday, April 14, 2012 12:45 PM
  • David - I'd be interested in seeing the code, seems like you and I might be working on a similar issue
    Sunday, July 1, 2012 2:42 PM
  • I am working on an issue like this too. So I am wondering if we can all team up to resolve this issue. this is what I have been able to do but I have been unable to write the modified message back. If you have any questions please let me know:

    static void ReadTextMessageBody(Stream myStream, Stream OutputStream, string signature)
            {
                
                try
                {
                    TnefReader reader = null;
                    TnefWriter writer = null;

                    try
                    {
                        reader = new TnefReader(myStream, 0, TnefComplianceMode.Loose);
                    }
                    catch (Exception ex)
                    {
                        
                        WriteExceptionToXML(ex.InnerException.Message);
                    }

                    writer = new TnefWriter(OutputStream, reader.AttachmentKey);

                    
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    TnefPropertyReader propReader;
                    
                    bool attributeAvailable;



                    // first read message-level properties

                    while ((attributeAvailable = reader.ReadNextAttribute()) && reader.AttributeLevel != TnefAttributeLevel.Attachment)
                    {
                        propReader = reader.PropertyReader;



                        if (reader.AttributeTag == TnefAttributeTag.RecipientTable)
                        {
                            // let's read the recipient table. It requires slightly different code to read, as it contains rows
                            // of properties instead of a flat list of properties like every other attribute.



                            while (propReader.ReadNextRow())
                            {
                                // we have found a new recipient entry



                                while (propReader.ReadNextProperty())
                                {
                                    // process a property of a recipient
                                    // snip...
                                }
                            }
                        }
                        else
                        {
                            // Read all message properties. Note: I am not bothering to check attribute IDs, it is not necessary. Every
                            // attribute can contain /certain/ properties and MapiProperties attribute can contain a list of /arbitrary/ properties
                            // but for processing purposes all those should just be the same, TnefReader will do any necessary conversion of
                            // "legacy" attributes into one or more MAPI properties.



                            while (propReader.ReadNextProperty())
                            {
                                if (propReader.PropertyTag.Id == TnefPropertyId.Body)
                                {
                                    // we have found a native plain text body property.



                                    string txt = propReader.ReadValueAsString(); //.GetRawValueReadStream();
                                   
                                   

                                    //// Let's output HTML body to a file
                                    //Stream outputStream = File.Create(logPath + "body.txt");

                                    //do
                                    //{
                                    //    bytesRead = textStream.Read(buffer, 0, buffer.Length);
                                    //    if (bytesRead != 0)
                                    //    {
                                    //        outputStream.Write(buffer, 0, bytesRead);
                                    //    }
                                    //}
                                    //while (bytesRead != 0);

                                    //StreamReader str = new StreamReader(outputStream);
                                    //string txt = str.ReadToEnd();
                                    txt += signature;
                                    try
                                    {
                                        writer.StartAttribute(TnefAttributeTag.Body, TnefAttributeLevel.Message);
                                        // writer.WriteAllProperties(propReader);
                                        writer.WriteAttributeRawValue(GetBytes(txt), 0, GetBytes(txt).Length);
                                        writer.Flush();
                                        writer.Close();
                                    }
                                    catch (Exception ex)
                                    {

                                        WriteExceptionToXML(ex);
                                    }

                                    WriteExceptionToXML(txt);

                                    //outputStream.Close();
                                    //textStream.Close();

                                    // this.PrintLine("found native TEXT body");
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {

                    WriteExceptionToXML("Cannot Read File: " + ex.InnerException.Message);
                }
            }

    Sunday, October 20, 2013 5:29 PM