Welcome to MSDN Blogs Sign in | Join | Help

FYI: Can’t use Exchange Impersonation with GetUserOofSettings, SetUserOofSettings, or GetUserAvailability

I kept forgetting about this so maybe blogging it will help me remember.  As this thread confirms – you can’t call GetUserOofSettings, SetUserOofSettings, or GetUserAvailability when using Exchange Impersonation.  If you try to do this you’ll get an error that the Service Account is not the mailbox owner.

Exchange API Team Blog: Exchange Impersonation vs. Delegate Access…

The Exchange API team has a new post to explaining the differences between using Exchange Impersonation vs. Delegate Access to access an Exchange mailbox using Exchange Web Services.  I’ve seen first hand that there is a gap in understanding the difference between the two and when to use one versus the other.  This post goes a long way to address some of the confusion.

An important note that some people miss is the differentiation between Windows and Exchange impersonation – they’re not the same thing and more importantly, they aren’t even related…

“Exchange Impersonation is different than Windows Impersonation. Windows Impersonation is an operating system concept that requires you to set Kerberos constrained delegation. Exchange Impersonation is a simpler authorization mechanism that is designed for use only within Exchange Web Services (EWS). For more information about Windows Impersonation, see Client Impersonation on MSDN.”

Conversely, the posts describes delegate access as…

“Delegate access is used in scenarios in which there needs to be a one-to-one relationship between users. One common application of delegate access is the sharing of calendars between users, such as when an admin manages an executive’s calendar, or a when handful of individuals working on a project need to coordinate calendars…

…Individual users can grant and remove delegate access to their own mailboxes through several mailbox clients, such as Microsoft Outlook, Outlook Web Access, or Exchange Web Services-based clients. A mailbox owner does not need administrator rights to grant another user delegate access to their mailbox.”

…To be clear, delegate access can be granted by an administrator on an entire mailbox (but not to specific folders within the mailbox) using the Add-MailboxPermission cmdlet but as the above statement points out individuals can also grant delegate access for other users to access specific folders in their mailbox or the entire mailbox.

A major difference between impersonation and delegation is that as far as Exchange Web Services and Outlook Web Access are concerned there is no way to assign a single delegate access to multiple mailboxes expect by establishing the delegate relationship for each individual mailbox.  I’ve talked about this before, the Exchange team describes it this way…

“For delegate access, there is no option to set up a single delegate for multiple mailboxes. A relationship must be established for each user who needs to access a given mailbox…

…You must explicitly grant delegate access for any new users who are added.”

…This is a great post and a worthwhile read but from working with many customers to help them understand these concepts, I’d like to add some additional information and emphasize some key points…

Understanding the Requests

To emphasize a side point in the blog post there are three methods of mailbox to access a mailbox: Exchange Impersonation, Delegate Access, and Direct Logon.  The article doesn’t provide examples of what the XML looks like in each type of access request using Exchange Web Services.  For understanding, I’ve provided them below…

Direct Logon

<GetFolder>
  <FolderShape>
    <t:BaseShape>AllProperties</t:BaseShape>
  </FolderShape>
  <FolderIds>
    <t:DistinguishedFolderId Id="inbox"/>
  </FolderIds>
</GetFolder>

Delegate Access

<GetFolder>
  <FolderShape>
    <t:BaseShape>AllProperties</t:BaseShape>
  </FolderShape>
  <FolderIds>
    <t:DistinguishedFolderId Id="inbox">
      <t:Mailbox>
        <t:EmailAddress>
         
someoneelse@contoso.com
        </t:EmailAddress>
      </t:Mailbox>
    </t:DistinguishedFolderId>
  </FolderIds>
</GetFolder>

Exchange Impersonation

<soap:Header>
  <t:ExchangeImpersonation>
    <t:ConnectingSID>
      <t:PrimarySmtpAddress>
        someone@contoso.com
      </t:PrimarySmtpAddress>
    </t:ConnectingSID>
  </t:ExchangeImpersonation>

</soap:Header>
<soap:Body>
  <GetFolder>
    <FolderShape>
      <t:BaseShape>AllProperties</t:BaseShape>
    </FolderShape>
    <FolderIds>
      <t:DistinguishedFolderId Id="inbox"/>
    </FolderIds>
  </GetFolder>
</soap:Body>

…The example above is for explicit delegate access.  You can also access a mailbox using implicit delegate access by specifying a FolderId which links to another mailbox.  All Ids returned by EWS embed their mailbox information in them so requesting them without explicitly specifying a mailbox will also work.

Putting It All Together

The terms used here come from the way Exchange Impersonation (also called Server-to-Server or S2S Authentication) is explained in the Inside Exchange Web Services book.  The book describes three types of accounts that come into play when accessing a mailbox: the Service account, the Act As account, and the Mailbox account.  Understanding how these accounts are affected by different mailbox access types is important.  The Service account is the account used to generate the XML requests and authenticate to the virtual directory, the Act As account is the account which will be used to authorize actions taken against a mailbox, and the Mailbox account is the actual resource you are trying to access.  In the grid below suppose you have an EWS application which runs as a user called ApplicationAccount, here is how the different access types affect each account context…

Access Type Service Account Act As Account Mailbox Account
Exchange Impersonation ApplicationAccount the impersonated account as specified in the ExchangeImpersonation property of the request any mailbox that the Act As account has mailbox rights to through Add-MailboxPermission or through permissions granted through Outlook, OWA, or EWS directly on a mailbox folder.
Delegate Access ApplicationAccount always ApplicationAccount any account or mailbox folder that ApplicationAccount has mailbox rights to through Add-MailboxPermission or through permissions granted through Outlook, OWA, or EWS directly on a mailbox folder.
Direct Logon ApplicationAccount always ApplicationAccount always ApplicationAccount

FYI: Issue with Windows 7 RC and VSTO…

The VSTO team has a post which details an issue that you might see when trying to deploy a VSTO solution to Windows 7 RC.  The error message would be…

“The required version of the .NET Framework is not installed on this computer”

As the blog post points out, the appropriate teams are aware of the issue and plan to address it by RTM of Windows 7.  Additionally they provide a workaround in the meantime.

Posted by mstehle | 1 Comments
Filed under: ,

FYI: Exchange 2007 SP2 Announced…

The Exchange team announced the upcoming service pack for Exchange 2007 recently.  The primary focus of this service pack is preparing Exchange 2007 for interoperability with Exchange 2010 but there are also fixes and changes to existing components and technologies that make this update worth installing when it comes out.  Here are some developer related points of interest that were announced:

Exchange Volume Snapshot Backup Functionality - A new backup plug-in has been added to the product that will enable customers to create Exchange backups when a backup is invoked through the Windows Server 2008 Backup tool. Exchange Server 2007 didn't have this capability on Windows Server 2008 and additional solutions were required to perform this task.

Named Properties cmdlets - SP2 enables Exchange administrators to monitor their named property usage per database.”

…Both of these are most useful to developers as debugging tools but they are valuable nonetheless.

Posted by mstehle | 1 Comments

INFO: EWS Tips and Tricks Links…

MVP Henning Krause has some good posts about various different tips and tricks with EWS that are worth looking at…

Searching a meeting with a specific UID using Exchange Web Services 2007

Resolving the primary email address with Exchange WebServices ResolveNames operation

Saving custom data on Exchange elements with The Exchange WebServices

…The UID and custom data posts are particularly insightful.  A key takeaway of the UID post is that “schema properties” or “first-class properties” such as UID often are calculated from or directly relate to “extended properties” or “MAPI properties”.  Often times you can workaround a limitation of schema properties by using the related extended property as Henning does with UID and GlobalObjectID.  In the saving custom data post, Henning gives some good background on named properties as well as why choosing the right property set is important…

"One property set is of particular interest, namely the one called PublicStrings. All custom properties created with Outlook are stored in this set. If a custom property is designed to be used by custom Outlook formulas, the developer must choose this property set. In any other case, it is better to create a random GUID and use that property set to prevent collisions with other applications.”

Posted by mstehle | 2 Comments

FYI: In Outlook 2010 Exchange Client Extensions Are Gone…

On the Outlook team blog it was announced recently that Exchange Client Extensions (ECEs) will not work with Outlook 2010.  This shouldn’t come as a big surprise, they haven’t been a primary extensibility model for Outlook since Outlook 97-98 days.  However, many Outlook developers have continued to maintain their ECEs and Microsoft supports them to work on Outlook versions up to Outlook 2007.  Here are the suggestions from the blog post about how to move forward…

“To redesign your solution, you should consider the following options:

  • Rewrite your ECE as a COM Add-in using native or managed code. Unlike ECEs, an add-in represents a strategic extensibility technology that is fully supported in Outlook 2010. Using an Outlook add-in, you can build Outlook form regions and extend the Office Fluent User Interface. For additional information, please visit the Outlook Developer Portal on MSDN.
  • Rewrite your ECE as a Windows service application using native code and MAPI. If you are writing a Windows service application, you must use MAPI to access Outlook items rather than the Outlook object model.”

FYI: Unlike Exchange 2007, Exchange 2010 will *not* ship a 32-bit version

The Exchange team announced that Exchange 2010 will not have a 32-bit version – not even for evaluations.  The post focuses on mainly the non-developer related impact of this change.  The key points for developers are:

> Applications that automate Exchange cmdlets locally will need to be compiled for 64-bit.

> 32-bit or 64-bit applications could leverage the remote Powershell capabilities in Powershell 2.0 to invoke the Exchange cmdlets remotely.

FYI: The Exchange Web Services Managed API is here!

As you might have seen, we announced last week that the Exchange Web Services Managed API is now available as a beta download.  I’ve been working with the API internally for about a year now and I must say, if you are writing .NET code that uses Exchange Web Services there’s no better way to do it.  I’ve been working on an Exchange browser application, similar to MFCMAPI which started out leveraging the auto-generated proxy classes.  Switching this project over to the EWS Managed API cut down hundreds of lines of code and improved the readability and organization of the code immensely.  (Stay tuned for more information on this project…)

From David Claux’s introduction:

“The EWS Managed API is a fully object-oriented API that provides the experience developers have come to expect from Visual Studio and the Microsoft .NET Framework. Built on the EWS XML protocol, it provides an easy-to-learn, easy–to-use, and easy-to-maintain .NET interface to Exchange Web Services that both beginner and advanced developers will find to be an improvement over autogenerated proxies.

Although the EWS Managed API is a new API (in the sense that it is a new .NET assembly that developers have to explicitly reference in their applications), it is important to understand that we are not replacing the EWS protocol. The EWS Managed API is just a complement to EWS for .NET developers. This means that your prior investments are safe. Whether you are using raw XML (if you are a Javascript developer, for example) or autogenerated proxies (on Windows or other operating systems) to talk to EWS, your existing EWS applications will continue to work. The EWS protocol is still the future of Exchange development; all new features will continue to be added to the EWS protocol, and will also be exposed in the EWS Managed API.”

The API at it’s simplest is just a set of proxy classes for interacting with Exchange – they still generate the same Exchange Web Services SOAP XML that the auto-generated proxy classes do or that your code that doesn’t use proxy classes do.  The difference is that this API is easy to use, understand, and has some additional business logic included.  The release of this API doesn’t change the support or our recommendations towards use of Exchange Web Services in their raw form.

Here are Matt’s three rules for getting started:

  1. Check out David Claux’s introduction for the API on MSDN as well as the “Working with…” section of the MSDN reference.
  2. Don’t confuse the Exchange Web Services Managed Reference with the EWS Managed API Reference.  The Exchange Web Services Managed Reference in the MSDN is reference of the auto-generated proxy classes.
  3. For the sake of my sanity please don’t start calling this the EWS MAPI – it’s the EWS Managed API, the EWS API, the EWS Client API, The API, Managed EWS, anything you want that doesn’t involve acronyms shared with other Exchange APIs.  (I guess I should be happy they didn’t call it EWS CDO, right?)

OOM.NET: Like a good standup comic – use scope and have good timing…

Recently, I was helping someone with a Outlook item leak type issue involving a Task FormRegion.  The symptom was that after opening a task, closing it, and reopening the item they were getting the infamous error message, “COM object that has been separated from its underlying RCW cannot be used.”  They were familiar with some of the issues discussed here and knew to call ReleaseCOMObject() on objects as they were done with them.  However, that is the only part of proper Outlook coding with .NET – you need to use scope and have good timing when you lay out .NET classes that handle events and use Outlook objects.  The following class is a simple example of that scope and timing…

Scope

As I pointed out in my original OOM.NET post, you need to consider the scope of the objects whose events you listen to.  The key is not to call ReleaseCOMObject() on an item while you are still listening to events from it.  In the class below, notice that _task is defined at the module level so that as long as the instance of TaskRegion is alive and listening to the Write() event _task will not go out of scope and get garbage collected by the CLR. 

Timing

It seems like there is a general understanding now that Outlook objects need to be released.  Additionally, you must *always decrement the event handlers that you add*.  However, the humor in a good joke is not just the punch line but also good timing – you have to be strategic about when to call ReleaseCOMObject() and when to decrement the event handler.  Since this is a FormRegion class I’m utilizing the FormRegionShowing event to initialize _task and add the event handler and I use FormRegionClosed to remove the event handler and release _task.  In a simple item wrapper class you might use the constructor and a dispose method in the same way.  The goal is not call ReleaseCOMObject() until the events are unhooked, that way the COM object become separated from your RCW that is still trying to handle events.

NOTE – I’m also employing Patrick’s fix in FormRegionInitializing for a FormRegion specific leak scenario…

partial class TaskRegion
{
    Outlook.TaskItem _task = null;

    #region Form Region Factory

    [Microsoft.Office.Tools.Outlook.FormRegionMessageClass
        (Microsoft.Office.Tools.Outlook.FormRegionMessageClassAttribute.Task)]
    [Microsoft.Office.Tools.Outlook.FormRegionName
        ("ReleaseTaskRegion.TaskRegion")]
    public partial class TaskRegionFactory
    {
        // Occurs before the form region is initialized.
        // To prevent the form region from appearing, set e.Cancel to true.
        // Use e.OutlookItem to get a reference to the current Outlook item.
        private void TaskRegionFactory_FormRegionInitializing(object sender,
            Microsoft.Office.Tools.Outlook.FormRegionInitializingEventArgs e)
        {
            Marshal.ReleaseComObject(e.OutlookItem);
        }
    }

    #endregion

    // Occurs before the form region is displayed.
    // Use this.OutlookItem to get a reference to the current Outlook item.
    // Use this.OutlookFormRegion to get a reference to the form region.
    private void TaskRegion_FormRegionShowing(object sender, System.EventArgs e)
    {
        _task = this.OutlookItem as Outlook.TaskItem;

        _task.Write += new Outlook.ItemEvents_10_WriteEventHandler(_task_Write);
    }

    private void _task_Write(ref bool Cancel)
    {
        System.Diagnostics.Debug.WriteLine("Write fired!");
    }

    // Occurs when the form region is closed.
    // Use this.OutlookItem to get a reference to the current Outlook item.
    // Use this.OutlookFormRegion to get a reference to the form region.
    private void TaskRegion_FormRegionClosed(object sender, System.EventArgs e)
    {
        _task.Write -= new Outlook.ItemEvents_10_WriteEventHandler(_task_Write);

        System.Runtime.InteropServices.Marshal.ReleaseComObject(_task);
    }
}

…This post is a continuation of my efforts to document common issues I’ve seen when .NET programmers write solutions with Outlook’s object model – be they separate executables, VSTO Add-ins, or Outlook FormRegions. To see all the posts in this series check out my posts with the OOM.NET tag…

…To check out more blog posts from Microsoft’s Messaging Developer Support team which supports Outlook, Exchange, and other email-related development using Microsoft APIs check out the DevMsgTeam tag across all MSDN blogs…

OOM.NET: Some day we’ll look back and laugh at the “Good Ole’ Days of Item Leaks”…

Misha Shneerson, a senior developer on the VSTO team, has a great post giving us hope in the next version of the .NET framework and the 4.0 CLR.  The feature name Misha uses gives it away, “NOPIA” means no interop assemblies! 

…This post is a continuation of my efforts to document common issues I’ve seen when .NET programmers write solutions with Outlook’s object model – be they separate executables, VSTO Add-ins, or Outlook FormRegions. To see all the posts in this series check out my posts with the OOM.NET tag…

…To check out more blog posts from Microsoft’s Messaging Developer Support team which supports Outlook, Exchange, and other email-related development using Microsoft APIs check out the DevMsgTeam tag across all MSDN blogs…

Posted by mstehle | 0 Comments

KB: XML schema validation errors when Exchange Web Service requests and responses have invalid XML characters

…I recently submitted the following KB article for publication but wanted to get the content out.  I will update this post when the KB article is published…

SYMPTOMS

Scenario 1

When you submit an Exchange Web Services request to Microsoft Exchange Server 2007 that contains invalid XML characters, you may get the following ErrorSchemaValidation exception response:

“The request failed schema validation: [character], hexadecimal value [value], is an invalid character.”

Scenario 2

When you submit an Exchange Web Services request to Microsoft Exchange Server 2007 that returns item properties in the response containing invalid XML characters using the Visual Studio auto-generated proxy classes, you may get the following InvalidOperationException exception:

”There is an error in XML document”

“[character] hexadecimal value [value], is an invalid character.”

CAUSE

It is possible for Exchange item properties to have values that contain characters outside the valid range in the XML specification which is defined here:

http://www.w3.org/TR/2000/WD-xml-2e-20000814#dt-character

When an Exchange Web Services client makes a request to retrieve that item property, the Exchange server encodes the property values to ensure the response is transmittable.  If this response is received by the client and subsequently validated against the XML schema, it will fail validation because of the invalid XML characters.

Conversely, if an Exchange Web Services client tries to send these characters in a request, even if they are encoded, the Exchange server will try and validate the request against the XML schema which will fail.

RESOLUTION

Scenario 1

There is no way to successfully post an Exchange Web Service request that contains invalid XML characters.  If you are updating a property’s value that contains invalid characters you must decide whether to replace or omit these characters.

Scenario 2

In order to read Exchange Web Service responses from Microsoft Exchange Server 2007 that contain invalid XML characters, you should skip XML validation. If you are using the Visual Studio auto-generated proxy classes for Exchange Web Services then you can create a special partial class which overrides the GetReaderForMessage method of the ExchangeServiceBinding class which turns XML validation off:

public partial class MyExchangeServiceBinding: ExchangeServiceBinding
{
    protected overrideXmlReader GetReaderForMessage(
        SoapClientMessage message,
        int bufferSize)
    {
        XmlReader retval = base.GetReaderForMessage(
            message,
            bufferSize);

        XmlTextReader xrt = retval asXmlTextReader;

        if(null!= xrt)
        {
            xrt.Normalization = false;
        }

        returnretval;
    }
}

FYI: Exchange 2007 OnSyncSave Store Events and Plain Text Message Don’t Play Nice

…Recently, I worked with a customer who was facing the problem described below.  We requested a fix but ended up finding a workaround using a Transport Agent.  At this time there is no plan to fix this issue in Exchange 2007…

Problem Description

In an OnSyncSave store event in Exchange 2007 every field's value of a plain text message which triggers the event returns the following exception in the first pre-commit event:

"Operation failed to complete and the status is unavailable. The field may be unavailable or the operation was not attempted."

When the OnSyncSave event fires again for this message after the message has been committed to the store the property values can be read.

NOTE: HTML and RTF messages do not have this problem - this only applies to Plain Text messages.

Workarounds

There are no straight forward workarounds, only other options which may or may not suit your application’s needs:

  • Use the OnSave asynchronous event sink.  The trade off here is obvious but the OnSave event doesn’t have any problem reading properties from a message that triggers the event.
  • Use a Transport Agent.  The main trade off here is that transport agents operate outside of the mailbox in the transport stack.  They can’t react to events that happen within a mailbox such as changing an item in the calendar or moving a message from one folder to another.  They only react to mail being delivered to or sent from a mailbox.
  • Use an Outlook Add-in.  The big trade-off here is that changes in OWA are not handled, you can only react to events that the user triggers in Outlook.
  • Use rules.  The main drawback is that they have a fixed set of functions but they don’t require any code to be written or bits to be deployed.

Again, depending on your solution one of these workarounds or combination of two might accomplish what you need.  An example of combining two of these options would be to use a transport agent to tag messages as they are sent to a particular mailbox and then use a rule to key off this tag and move those messages to a specific folder on deliver.

Posted by mstehle | 1 Comments

HOWTO: Sample Transport Agent – Add Headers, Categories, MAPI Props, Even Uses a Fork!

Welcome to my first in depth Exchange Transport Agent sample!  If you are not familiar with Exchange Transport Agents this blog post is a great place to start.  I created a sample Transport Agent for a customer a while ago that I recently added commenting to and cleaned up so it could serve as a general demo suitable for this blog. The focus of this sample is to illustrate a several techniques but no necessarily serve as a template or best practice for writing transport agents in general.  The key techniques illustrated are:

  1. Forking a message in the “OnSubmittedMessage” event to process a messages individually for each recipient.
  2. Adding a custom header to a message.
  3. Adding a Keywords (also known as Category) header to a message
  4. Adding a custom named property to a MAPI message

The comments in the code speak for themselves so I won’t walk through it all.  There are two very important notes regarding the Keyword header and adding MAPI properties that I want to emphasize though.

The Keywords Header is Removed By Default

If you are you trying to add the Keywords header in a transport agent or even just when sending MIME messages to Exchange 2007 keep in mind that this header is translated into Categories which are removed by default during content conversion.  If you really need this functionality though, you can turn this off using the Set-TransportConfig cmdlet and setting ClearCategories to $false.  Click here to read more.

If You Don’t Get TNEF, You Don’t Have TNEF

There is no API to convert MIME messages to TNEF within a transport agent.  The TNEF body part must be a full representation of the message being submitted to Exchange.  You can’t have a complex MIME message with a TNEF body part that simply adds a MAPI property or two.  Messages that are submitted from outside the organization will mostly like not have TNEF, there is no mechanism to create a representative TNEF body part for these messages and then append properties.

One final note is that this sample logs information out to a text file on the system.  Keep in mind that transport agents run in the context of the Exchange Transport Service which is typically Network Service.  In this case, the Network Service account would need write permissions to whatever folder the log file is configured to write to. 

Enjoy…

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Routing;
using Microsoft.Exchange.Data.Common;
using Microsoft.Exchange.Data.Mime;
using Microsoft.Exchange.Data.ContentTypes.Tnef;

namespace MyRoutingAgent
{

    public sealed class MyRoutingAgentFactory : RoutingAgentFactory
    {
        public override RoutingAgent CreateAgent(SmtpServer server)
        {
            return new MyRoutingAgent();
        }
    }

    public class MyRoutingAgent : RoutingAgent
    {
        // Place this text in the subject to test the agent
        public const string TEST_TOKEN = "[TEST]";
        public const string LOG_FILE_PATH = @"C:\Users\Administrator\Desktop\MyRoutingAgent\MyRoutingAgent.log";

        public MyRoutingAgent()
        {
            this.OnRoutedMessage += new RoutedMessageEventHandler(MyRoutingAgent_OnRoutedMessage);
            this.OnSubmittedMessage += new SubmittedMessageEventHandler(MyRoutingAgent_OnSubmittedMessage);
            this.OnResolvedMessage += new ResolvedMessageEventHandler(MyRoutingAgent_OnResolvedMessage);
        }

        void MyRoutingAgent_OnSubmittedMessage(SubmittedMessageEventSource source, QueuedMessageEventArgs e)
        {
            // Don't process EVERY message...
            if (e.MailItem.Message.Subject.Contains(TEST_TOKEN))
            {
                // If we have multiple recipients, fork the message to process each recipient seperately...
                if (e.MailItem.Recipients.Count > 1)
                {
                    LogMessage("OnSubmittedMessage", e.MailItem, "Forking message with " + e.MailItem.Recipients.Count + " recipients.");

                    int count = 0;
                    while (e.MailItem.Recipients.Count > 1)
                    {
                        // Create an individual forked message for each recipient...
                        EnvelopeRecipient recip = e.MailItem.Recipients[e.MailItem.Recipients.Count - 1];
                        List<EnvelopeRecipient> recips = new List<EnvelopeRecipient>();
                        recips.Add(recip);
                        source.Fork(recips);
                        count++;
                    }

                    // This should always be one.
                    LogMessage("OnSubmittedMessage", e.MailItem, "Forked " + count + " messages.");
                }
                else
                {
                    LogMessage("OnSubmittedMessage", e.MailItem, "Single recipient, no forking needed.");
                }
            }
        }

        void MyRoutingAgent_OnResolvedMessage(ResolvedMessageEventSource source, QueuedMessageEventArgs e)
        {
            // Don't process EVERY message...
            if (e.MailItem.Message.Subject.Contains(TEST_TOKEN))
            {
                MimePart tnefPart = e.MailItem.Message.TnefPart;
                if (tnefPart != null)
                {
                    LogMessage("OnResolvedMessage", e.MailItem, "TNEF");
                }
                else
                {
                    LogMessage("OnResolvedMessage", e.MailItem, "No TNEF");
                }
            }
        }
        
        void MyRoutingAgent_OnRoutedMessage(RoutedMessageEventSource source, QueuedMessageEventArgs e)
        {
            // Don't process EVERY message...
            if (e.MailItem.Message.Subject.Contains(TEST_TOKEN))
            {
                if (e.MailItem.Recipients.Count == 1)
                {
                    LogMessage("OnRoutedMessage", e.MailItem, "Processing message sent to " + e.MailItem.Recipients[0].Address.LocalPart + ".");

                    // If we haven't already processed this message...
                    if (e.MailItem.Message.MimeDocument.RootPart.Headers.FindFirst("MyHeader") == null)
                    {
                        ProcessMailItem(e.MailItem);
                    }
                    else
                    {
                        LogMessage("OnRoutedMessage", e.MailItem, "Already processed this message, skipping.");
                    }
                }
                else
                {
                    LogMessage("OnRoutedMessage", e.MailItem, "Unexpected number of recipients, " + e.MailItem.Recipients.Count.ToString() + ".");
                }
            }
        }

        /// <summary>
        /// Add a custom header, keyword header, and MAPI
        /// named property into PS_PUBLIC_STRINGS if there
        /// is a TNEF body part.
        /// </summary>
        private void ProcessMailItem(MailItem item)
        {
            // Modify the subject of this message to ensure the
            // fork worked.  Append the first recipient name to the 
            // subject.  This should match the mailbox name the
            // message is delivered to.
            item.Message.Subject = item.Message.Subject + "| To: " + item.Recipients[0].Address.LocalPart;

            MimeDocument mdMimeDoc = item.Message.MimeDocument;
            HeaderList hlHeaderlist = mdMimeDoc.RootPart.Headers;
            MimeNode lhLasterHeader = hlHeaderlist.LastChild;

            // Add a custom header
            TextHeader nhNewHeader = new TextHeader("MyHeader", "MyHeaderValue");
            hlHeaderlist.InsertBefore(nhNewHeader, lhLasterHeader);

            // Add the Keywords header to set a Category in Outlook/OWA.
            // *** INFO ***
            // Keywords (also known as Categories) are stripped from
            // message by default.  This can be disabled using the 
            // "Set-TransportConfig –ClearCategories $false" cmdlet.
            // (http://technet.microsoft.com/en-us/library/bb124151.aspx)
            TextHeader nhNewKeywords = new TextHeader("Keywords", "1");
            hlHeaderlist.InsertBefore(nhNewKeywords, lhLasterHeader);

            MimePart tnefPart = item.Message.TnefPart;

            // Without a TNEF body part, we can't do this step.
            // There is no way to create a TNEF body part from
            // scratch if Exchange isn't giving us one.  Most
            // mail that comes from the internet won't have TNEF.
            if (tnefPart != null)
            {
                TnefReader reader = new TnefReader(tnefPart.GetContentReadStream());
                TnefWriter writer = new TnefWriter(
                    tnefPart.GetContentWriteStream(tnefPart.ContentTransferEncoding),
                    reader.AttachmentKey,
                    0,
                    TnefWriterFlags.NoStandardAttributes);

                while (reader.ReadNextAttribute())
                {
                    if (reader.AttributeTag == TnefAttributeTag.MapiProperties)
                    {
                        writer.StartAttribute(TnefAttributeTag.MapiProperties, TnefAttributeLevel.Message);
                        writer.WriteAllProperties(reader.PropertyReader);

                        int tag;
                        unchecked
                        {
                            // The first four bytes of the tag is must be at least 0x8000 
                            // when setting a named property, the second determines the type. 
                            // http://www.cdolive.com/cdo10.htm
                            tag = (int)0x8000001E;
                        }

                        Guid PS_PUBLIC_STRINGS = new Guid("00020329-0000-0000-C000-000000000046");

                        writer.StartProperty(
                            new TnefPropertyTag(tag),
                            PS_PUBLIC_STRINGS,
                            "MyProp");
                        writer.WritePropertyValue("Hello!");
                    }
                    else
                    {
                        writer.WriteAttribute(reader);
                    }
                }

                //  Close writer
                if (null != writer)
                {
                    writer.Close();
                }

                LogMessage("ProcessMailItem", item, "Added 'MyProp' to PS_PUBLIC_STRINGS.");
            }
            else
            {
                LogMessage("ProcessMailItem", item, "No TNEF, not adding property");
            }
        }

        private void LogMessage(string eventName, MailItem item, string message)
        {
            TextWriter tw = System.IO.File.AppendText(LOG_FILE_PATH);
            tw.WriteLine(DateTime.Now.Ticks + "\t" + eventName + " - " + item.Message.Subject + " - " + message);
            tw.Close();
        }
    }

}

Jason Henderson: A Brief History and Exciting Future for Exchange Development

It took me forever to get around to watching this but this is just a great presentation about Exchange development’s future at PDC.  He does a great job of explaining where we’ve been as well…

Learn about the Exchange Web Services Managed API and how Exchange is getting “Cloud Ready” at PDC’08

This is an absolute must watch for any Exchange developer!

FYI: The MAPI docs have moved...

The MAPI docs have been revamped and moved from Exchange to Outlook 2007 in MSDN.  As Thom points out - they did great work on these updated docs.  Steve also has a post about this along with an overview.  If you are interested in MAPI, go check them out!

Posted by mstehle | 3 Comments
Filed under: ,
More Posts Next page »
 
Page view tracker