Writing Managed Sinks for SMTP and Transport Events

White Paper

Abstract

This document provides an overview of how to write event sinks for SMTP and Transport events in managed code using wrappers that encapsulate some of the details of communicating with the unmanaged server. The instructions in this document provide steps for writing managed event sinks using both Visual Microsoft Visual Studio®.NET 2003 with the Microsoft®.NET Framework 1.1 or Visual Microsoft Visual Studio®.NET 2005 with the Microsoft®.NET Framework 2.0.

Published: March 2003

Updated: July 2006


Table of Contents

1

Introduction 1

How to Write a Managed Event Sink in C# – Step by Step 1

ShieldsUp Sample Managed Sink 7

Debugging Managed Sinks 9

Managed Wrapper Interfaces 9

Interface:SmtpInCommandContext 10

Interface:SmtpOutCommandContext 10

Interface:SmtpServerResponseContext 11

Interface:MailMsgPropertyBag 11

Interface:Message 12

Interface:RecipsAdd 13

Additional Resources 14


Writing Managed Sinks for SMTP and Transport Events

White Paper

Published: March 2003

Updated: July 2006

For the latest information, please see http://msdn.microsoft.com/exchange/.

Introduction

Writing event sinks in managed code allows the programmer to make use of the Microsoft®.NET Framework and to more efficiently write the code that is necessary for the sink. Writing managed sinks can be somewhat difficult, however, because event sink interfaces were designed primarily for C++ programmers and are therefore not very easy to use in a language such as C#. In addition, some of the methods that are imported using Tlbimp.exe will not work unless they are modified at the intermediate language (IL) level. However, these inconsistencies in the imported assemblies have been fixed in the events’ primary interop assembly (PIA) provided, and other interfaces that were inconvenient to use have also been wrapped so that they are easier to use from managed code.

These managed wrappers wrap each of the methods on the original interfaces and correctly communicate with the unmanaged server. In addition, some of the original methods are exposed as properties instead of pairs of methods. For example, all pairs of Set and Query methods are exposed as properties.

This document assumes that the reader has a working knowledge of the .NET Framework, COM, COM Interop, and Microsoft Windows®2000 SMTP Service Events.

For information about event sinks in general, see Microsoft Windows2000 SMTP Service Events. As described in that document, sinks can implement a number of interfaces to handle corresponding events on the server. The methods of those interfaces are then called when the appropriate event is triggered and certain parameters are passed in.

How to Write a Managed Event Sink in C# – Step by Step

To write a managed event sink, you must link to the PIA that contains the necessary interfaces and implement the interfaces that correspond to the events that need to be handled. Optionally, you can link to the assembly that contains the easier-to-use wrappers for these interfaces.

The following example illustrates how to write a managed SMTP event sink in C# that handles inbound commands. Use this type of sink to handle inbound SMTP commands and messages, and to process them as needed.

This example can be run on a computer running Windows2000 Server or Windows Server 2003 running the SMTP service, or on a computer running Microsoft Exchange2000 Server or Exchange 2003 Server. The code contained in this example must be built using Microsoft Visual Studio®.NET 2003 or Microsoft Visual Studio®.NET 2005, and can be downloaded from http://www.microsoft.com/downloads.

Build the Primary Interop Assembly

1.  Rename the .diff file corresponding to the version of Visual Studio you are using.

If you are using Microsoft Visual Studio®.NET 2003 rename the file named Microsoft.Exchange.Transport.EventInterop.VS2003.diff to Microsoft.Exchange.Transport.EventInterop.diff.

If you are using Microsoft Visual Studio®.NET 2005 rename the file named Microsoft.Exchange.Transport.EventInterop.VS2005.diff to Microsoft.Exchange.Transport.EventInterop.diff.

2.  Open a Visual Studio command prompt.

If you are using Microsoft Visual Studio®.NET 2003, click Start à All Programs à Microsoft Visual Studio .NET 2003 à Visual Studio .NET Tools à Visual Studio .NET 2003 Command Prompt.

If you are using Microsoft Visual Studio®.NET 2005, click Start à All Programs à Microsoft Visual Studio .NET 2005 à Visual Studio .NET Tools à Visual Studio .NET 2005 Command Prompt.

3.  In the Visual Studio command prompt, navigate to the C:\Program Files\Exchange SDK\ManagedSinksWP\Interop directory.

4.  Run nmake.exe to build the PIA, Microsoft.Exchange.Transport.EventInterop.dll.

Note: You can safely ignore any warnings you see when you run nmake.exe.

Build the Wrappers

1.  Copy Microsoft.Exchange.Transport.EventInterop.dll to the wrappers directory (\ManagedSinksWP\Wrappers).

2.  In the same command line used to build the PIA, compile and sign the wrappers assembly, Microsoft.Exchange.EventWrappers.dll, so it has a strong name. If you are using Microsoft Visual Studio®.NET 2005, you can specify a key file with an option when you run the compiler. This allows you to sign the assembly when you build it.

If you are using Microsoft Visual Studio®.NET 2003, you must first run the compiler to create a module and then use al.exe with the module and a key file to create the signed assembly.

Below are the commands to run for each version of Visual Studio:

Microsoft Visual Studio®.NET 2005

csc /t:library /out:Microsoft.Exchange.Transport.EventWrappers.dll /r:Microsoft.Exchange.Transport.EventInterop.dll /keyfile:..\Interop\Microsoft.Exchange.Transport.EventInterop.snk *.cs /unsafe

Microsoft Visual Studio®.NET 2003

First, run this command to create the module:

csc /t:library /out:Microsoft.Exchange.Transport.EventWrappers.Module.dll /target:module /r:Microsoft.Exchange.Transport.EventInterop.dll *.cs /unsafe

Then run this command to create the signed assembly:

al Microsoft.Exchange.Transport.EventWrappers.Module.dll /target:lib /out:Microsoft.Exchange.Transport.EventWrappers.dll /keyfile:..\Interop\Microsoft.Exchange.Transport.EventInterop.snk

You can use the primary interop assembly, Microsoft.Exchange.Transport.EventInterop.dll, and the wrappers assembly, Microsoft.Exchange.Transport.EventWrappers.dll, as references for all the event sinks you implement with managed code. You can use either Microsoft Visual Studio®.NET 2003 or Microsoft Visual Studio®.NET 2005 to implement event sinks, regardless of the version of Microsoft Visual Studio®.NET and the .NET Framework you used to build the two assemblies.

Write the Event Sink

1.  Start Microsoft Visual Studio®.NET.

2.  On the File menu, click New, and then click Project.

3.  Under Project Types, click Visual C# Projects.

4.  Under Templates, click Class Library.

5.  In the Name textbox in the New Project dialog, type the following for the project name:

ManagedSinks

6.  Choose a location for the project and then click OK to create the ManagedSinks project.

7.  In Solution Explorer, right-click the project, and then click Properties. The following steps differ between Microsoft Visual Studio®.NET 2003 and Microsoft Visual Studio®.NET 2005.

Microsoft Visual Studio®.NET 2003

  1. Click the Configuration properties folder.
  2. Set the value of Register for COM interop to true.
  3. Click OK to close the project properties dialog.

Microsoft Visual Studio®.NET 2005

  1. Click the Build tab
  2. Check the Register for COM interop checkbox.
  3. Close the project properties dialog.

8.  Set up the project to sign the assembly with a strong name using the provided key file named Microsoft.Exchange.Transport.EventInterop.snk.

Microsoft Visual Studio®.NET 2003

  1. In Solution Explorer, double-click AssemblyInfo.cs to open it.
  2. Set the value of the Assembly as shown below.

[assembly: AssemblyKeyFile(@"C:\Program Files\Exchange SDK\ManagedSinksWP\Interop\Microsoft.Exchange.Transport.EventInterop.snk")]

  1. Save and close AssemblyInfo.cs.

Microsoft Visual Studio®.NET 2005

  1. In Solution Explorer, right-click on the project and then click Properties.
  2. Click the Signing tab.
  3. Check the Sign the assembly check box.
  4. Click the drop-down list labeled Choose a strong name key file: and then click <Browse…>
  5. Browse to and select C:\Program Files\Exchange SDK\ManagedSinksWP\Interop\Microsoft.Exchange.Transport.EventInterop.snk and then click Open.
  6. Click File and then click Save to save your changes.

9.  In Solution Explorer, right-click References, and then click Add Reference.

10. Add references to the PIA and wrappers assemblies to your project (both are in the \ManagedSinksWP\Wrappers directory):

·  Microsoft.Exchange.Transport.EventInterop.dll

·  Microsoft.Exchange.Transport.EventWrappers.dll

11. In Solution Explorer, right-click the Visual Studio generated class file named Class1.cs and then click Delete.

12. In Solution Explorer, right-click the project, and then click Add, and then click Class…

13. In the Name textbox in the Add New Item dialog, type the following for the name of the new class file:

EventSink.cs

14. Click Add to add EventSink.cs to the project.

15. In the source code for EventSink.cs, add using statements for the two namespaces included in the PIA and wrapper assemblies and the System.Runtime.InteropServices namespace. For example:

using System;

using System.Runtime.InteropServices;

using Microsoft.Exchange.Transport.EventInterop;

using Microsoft.Exchange.Transport.EventWrappers;

16. Modify the EventSink class so it inherits from the ISmptInCommandSink interface. For example:

namespace ManagedSinks

{

public class EventSink : ISmtpInCommandSink

{

}

}

17. Implement the ISmtpInCommandSink.OnSmtpInCommand method in the EventSink class. For example:

namespace ManagedSinks

{

public class EventSink : ISmtpInCommandSink

{

void ISmtpInCommandSink.OnSmtpInCommand(

object pServer,

object pSession,

MailMsg pMsg,

ISmtpInCommandContext pContext)

{

}

}

}

18. Generate a new GUID and add it as a GuidAttribute of the EventSink class.

a.  Click Tools, and then click Create GUID.

b.  Select Registry Format, click New GUID, and then click Copy to copy the new GUID to the Clipboard.

c.  Paste this new GUID into your code (the shaded section in the example below)

Note: Exclude the enclosing braces ({}) around the GUID.

For example:

namespace ManagedSinks

{

[Guid(“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”)]

public class SampleInboundSink : ISmtpInCommandSink

{

void ISmtpInCommandSink.OnSmtpInCommand(

object pServer,

object pSession,

MailMsg pMsg,

ISmtpInCommandContext pContext)

{

}

}

}

19. Inside the OnSmtpInCommand method implementation, instantiate managed wrappers (the shaded section in the example) for each of the parameters for the method. For example:

namespace ManagedSinks

{

[Guid(“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”)]

public class SampleInboundSink : ISmtpInCommandSink

{

void ISmtpInCommandSink.OnSmtpInCommand(

object pserver,

object pSession,

MailMsg pMsg,

ISmtpInCommandContext pContext)

{

MailMsgPropertyBag Sever = new MailMsgPropertyBag(pServer);

MailMsgPropertyBag Session = new MailMsgPropertyBag(pSession);

Message Msg = new Message(pMsg);

SmtpInCommandContext Context = new SmtpInCommandContext(pContext);

}

}

}

20. Build the project, which also registers the object for COM Interop.

21. If the object is built on a non-production server, copy ManagedSinks.dll and Microsoft.Exchange.Transport.EventInterop.dll to the production server and use the RegAsm Visual Studio tool to register the assembly on the server. On the production server, open up a command window and navigate to the folder containing the ManagedSinks.dll. Type the following from the command prompt:

RegAsm.exe ManagedSinks.dll /codebase

22. Register the object on the production server as an event sink by using the smtpreg.vbs script available on MSDN.

The syntax of the smtpreg script is as follows:

usage: cscript smtpreg.vbs <Command> <Arguments>

Commands:

/add <Instance> <Event> <DisplayName | Binding GUID> <SinkClass> <Rule>

/remove <Instance> <Event> <DisplayName | Binding GUID>

/setprop <Instance> <Event> <DisplayName | Binding GUID> <PropertyBag> <PropertyName> <PropertyValue>

/delprop <Instance> <Event> <DisplayName | Binding GUID> <PropertyBag> <PropertyName>

/enable <Instance> <Event> <DisplayName | Binding GUID>

/disable <Instance> <Event> <DisplayName | Binding GUID>

/enum

Arguments:

<Instance> The SMTP virtual service instance

<Event> The event name. Can be one of the following:

Transport Events:

StoreDriver

OnArrival (OnTransportSubmission)

OnTransportSubmission

OnPreCategorize

OnCategorize

OnPostCategorize

OnTransportRouter

MsgTrackLog

DnsResolver

MaxMsgSize

Protocol Events:

OnInboundCommand

OnServerResponse

OnSessionStart

OnMessageStart

OnPerRecipient

OnBeforeData

OnSessionEnd

<DisplayName> The display name of the event to edit

<SinkClass> The sink Programmatic identifier

<Rule> The protocol rule to use for the event (ehlo=*,mail from=*, etc)

<Binding GUID> The event binding GUID in registry string format: {GUID}

<PropertyBag> The "Source" or "Sink" property bag

<PropertyName> The name of the property to edit

<PropertyValue> The value to assign to the property

In this case, an event binding needs to be added. Use the /add command to add the binding. The Instance parameter asks for a virtual server instance; use the default virtual server instance of 1. The event that is being handled by this sample sink is the OnInboundCommand protocol event. The DisplayName can be any string that describes the sink, enclosed in quotation marks. The SinkClass is the fully qualified name of the exported COM object, which is the name of the enclosing namespace followed by a period and the name of the class. The name of the object in this example is ManagedSinks.EventSink. The final parameter describes what commands the sink will respond to, which, in this example, is the EHLO SMTP command. The following is an example of the event binding:

cscript smtpreg.vbs /add 1 OnInboundCommand “Sample managed sink” ManagedSinks.SampleInboundSink EHLO

The following screenshot shows the command and the resultant success message from the ShieldsUp Sample Managed Sink sample below.

ShieldsUp Sample Managed Sink

This sink handles the OnInboundCommand protocol event, as did the example in the previous section. The ShieldsUp sink listens for the EHLO command sent to the virtual server and immediately drops the connection after responding to the sender with an error message. The managed code below can be implemented by following the steps in How to Write a Managed Event Sink in C# – Step by Step, above.

using System;

using System.Runtime.InteropServices;

using Microsoft.Exchange.Transport.EventInterop;

using Microsoft.Exchange.Transport.EventWrappers;

namespace SampleManagedSink

{

[Guid("8277680B-D636-4054-8BF6-FD91F1EBC1CD")]

// ComVisible enables COM visibility of this class. The default is true.

// Explicitly setting this attribute to true, as shown below, is useful

// if ComVisible is set to false for the namespace and you want the

// classes to be accessible individually.

[ComVisible(true)]

public class ShieldsUp :

ISmtpInCommandSink,

IEventIsCacheable

{

//

// IEventIsCacheable methods

//

void IEventIsCacheable.IsCacheable()

{

// This will return S_OK by default.

}

//

// ISmtpInCommandSink methods

//

void ISmtpInCommandSink.OnSmtpInCommand(

object pServer,

object pSession,

MailMsg pMsg,

ISmtpInCommandContext pContext)

{

try

{

// Instantiate a wrapper for the context object.

SmtpInCommandContext Context = new SmtpInCommandContext(pContext);

// The next line uses properties on the wrapper that

// will be translated to calls to the proper interface.

Context.Response = "Please try again later.";

// The ProtocolEventConstants class contains several constants

// that can be used for the value of CommandStatus, including

// the constant used here that tells the server to drop the

// session.

Context.CommandStatus = (uint) ProtocolEventConstants.EXPE_DROP_SESSION;

// To return a value other than S_OK, throw a COMException.

throw new COMException("Event is consumed", ProtocolEventConstants.EXPE_S_CONSUMED);

}

// Release the COM objects.

finally

{

if (null != pServer)

{

Marshal.ReleaseComObject(pServer);

}

if (null != pSession)

{

Marshal.ReleaseComObject(pSession);

}

if (null != pMsg)

{

Marshal.ReleaseComObject(pMsg);

}

if (null != pCcontext)

{

Marshal.ReleaseComObject(pContext);

}

}

}

}

}

The following screenshot shows the message a user receives after connecting to the host computer using telnet on port 25 and typing the EHLO command.