Introduction
I use Event Sinks with Exchange Server 2003 frequently. Most of these were written in VB6 and I wanted to begin migrating them over to .Net and use Visual Studio 2005 as the development tool. In searching for coding help I encountered the article Developing Managed Event Sinks/Hooks for Exchange Server Store using C# by Logu Krishnan here at The Code Project. Although written in C# it was very illuminating as to the process required. The steps to do this project in VB.Net are not drastically different but they do require some differences. When I searched other areas for code samples, I never found any that did as good a job as Logu Krishnan had done with his article.
Exchange Store Events
This article focuses on Asynchronous Events. Specifically the events are OnSyncSave
and OnSyncDelete
. The article by Logu Krishnan previously referenced has an excellent description of Exchange Store Events.
The Purpose of the Sink
This sink is designed to be implemented against a mail enabled public folder. It fires each time a message is saved or deleted in the folder. The code extracts any attachments from the message, saves them to a target network location, copies the message to a sub folder named "Processed" and then deletes it from the original folder. I have not included the code for actually processing the attachment but it is fairly straightforward and I do a variety of tasks with the attachments based on the purpose of the folder. I have included detailed steps to create and implement the sink.
Create the Project
Create your project using the Windows Class Library template. I have named this project EventSinkNet.
Mark for COM Interop
Be sure to mark the project to Register for Comp Interop by clicking on Project from the menu, choosing
EventSinkNet Properties, selecting the Compile tab on the left and clicking on Register for COM Interop at the bottom.
Copy Required Files to Project
- Copy adodb.dll (file version 7.10.3077.0) from \Program Files\Microsoft.NET\Primary Interop.Assemblies to project directory.
- Copy codex.dll (Microsoft CDO for Microsoft Exchange Library - file version 6.5.7650.29) from \Program Files\Common Files\Microsoft Shared\CDO\cdoex.dll. This was on my Exchange Server.
- Copy exoledb.dll (Microsoft Exchange OLEDB Server - file version 6.5.7651.60) from \exchsrvr\bin\exoledb.dll). This was on my Exchange Server.
- Copy exevtsnk.tlb (Type Library - Size 11,976 bytes) from \Program Files\Exchange SDK\SDK\Support\OLEDB\exevtsnk.tlb
Create Strong Name Keys
Use the Visual Studio Command Prompt and navigate to the project folder. Create the strong name keys with the following commands.
- sn.exe -k adodb.key
- sn.exe -k cdoex.key
- sn.exe -k exoledb.key
- sn.exe -k exevtsnk.key
Create Interop Assemblies
- Open the Visual Studio 2005 Command Prompt and navigate to the project folder.
- Key in the following command to create the exoledb assembly
tlbimp exoledb.dll /keyfile:exoledb.key /out:interop.exoledb.dll
The output from the command may be reflect the following error but can be ignored
- Key in the following command to create the cdoex assembly
tlbimp cdoex.dll /keyfile:cdoex.key /out:interop.cdoex.dll
The output from the command should reflect the following
- Key in the following command to create the exevtsnk assembly
tlbimp exevtsnk.dll /keyfile:exevtsnk.key /out:interop.exevtsnk.dll
The output from the command may be reflect the following error but can be ignored
Add References to Your Project
Add references to the four dll's that are in your project directory.
Add a reference to System.EnterpriseServices
Your reference section in you Solution Explorer should appear as follows
Modify the AssemblyInfo.vb File
Add the following to the top of the file
Imports System.EnterpriseServices
Insert the following lines
[Assembly: AssemblyKeyFile(
"EventSinkNet.snk")]
[Assembly: AssemblyKeyName(
"EventSinkNet")]
[Assembly: ApplicationActivation(ActivationOption.Server)>
[Assembly: ApplicationName(
"EventSinkDLL")>
The Code
The sink acts on each message after it is saved to the store. It opens each message and extracts each attachment to a file on a network share. It then copies the message to a subfolder named "Processed" and deletes the original message.
Option Explicit On
Option Strict On
Imports System.IO
Imports System.EnterpriseServices
Imports Exoledb = Interop.Exoledb
Imports ExevtsnkLib = Interop.Exevtsnk
Imports CDO = Interop.cdoex
Imports ADODB
Namespace EvSink
Public Class ASyncEvents
Inherits ServicedComponent
Implements Exoledb.IExStoreAsyncEvents
Private Const LOGFILE As String = "C:\\evtlog.txt"
Private Const WORKPATH As String = "\\apps00\
shared$\SSWork\"
Public Sub OnDelete(ByVal pEventInfo As interop
.exoledb.IExStoreEventInfo, _
ByVal bstrURLItem As String, ByVal lFlags
As Integer) _
Implements interop.exoledb.IExStoreAsyncEvents
.OnDelete
' do something here
End Sub
Public Sub OnSave(ByVal pEventInfo As interop
.exoledb.IExStoreEventInfo, _
ByVal bstrURLItem As String, ByVal lFlags
As Integer) _
Implements interop.exoledb.IExStoreAsyncEvents
.OnSave
Dim sr As StreamWriter
Dim msg As CDO.IMessage = New CDO.Message
Dim atch As CDO.IBodyPart
Dim rec As ADODB.Record
Dim i As Integer
Dim sURLSuffix As String
Dim sURLPrefix As String
Dim sURLItemTo As String
Dim sAtch As String
' Open the log file, append text to file.
sr = File.AppendText(LOGFILE)
sr.WriteLine("In onsave")
' Parse out the components of the URL
i = InStrRev(bstrURLItem, "/", -1,
CompareMethod.Text)
sURLSuffix = Right(bstrURLItem, Len(
bstrURLItem) - i)
sURLPrefix = Left(bstrURLItem, i)
sURLItemTo = sURLPrefix & "processed/" &
sURLSuffix
' Get the message
Try
msg.DataSource.Open(bstrURLItem, _
Nothing, _
ConnectModeEnum.adModeRead, _
RecordCreateOptionsEnum
.adFailIfNotExists, _
RecordOpenOptionsEnum
.adOpenSource, _
Nothing, Nothing)
sr.WriteLine("Opened message")
' process it
If msg.Attachments.Count > 0 Then
For Each atch In msg.Attachments
sAtch = atch.FileName
atch.SaveToFile(WORKPATH & sAtch)
sr.WriteLine("Saved as: " & sAtch)
Next
End If
' save it to processed folder
msg.DataSource.SaveTo(sURLItemTo)
sr.WriteLine("Saved to: " & sURLItemTo)
atch = Nothing
msg = Nothing
' delete it
rec = New ADODB.Record
rec.Open(sURLPrefix, , ConnectModeEnum
.adModeReadWrite)
rec.DeleteRecord(sURLSuffix)
sr.WriteLine("Deleted record")
rec.Close()
Catch ex As Exception
sr.WriteLine("Record Delete Exception
message: " & ex.Message)
End Try
sr.Close()
End Sub
End Class
End Namespace
Create a Key Pair for the DLL
sn -k EventSinkNet.snk
Compile and Copy
I had to manually copy the adodb.dll to my Debug folder. Now copy the files from your Debug or Release folder to a folder on the Exchange Server.
Register the Assembly
On the Exchange Server, open a command prompt in the folder containing the sink. Run the regasm.exe command as shown below. The command should indicate "Types registered successfully".
Regasm.exe EventSinkNet.dll /codebase
Create the Component Service
Open the Administrative Tools/Component Services to install the dll. I create an empty application named "EventSinkNet" and clicked through the default options. I assigned a user to the Application Identity account to run the sink.
Install the Component
I then installed the component by select the Component Folder under the new component named "EventSinkNet" and choosing the "New" option under the properties. I selected "Import component(s) that are already registered" and located the component named EventSinkNet.EvSink.ASyncEvents
.
Register the Sink
The sink can be registered to any Exchange folder. A script file like the one below can be used to register the sink or you can use Exchange Explorer with comes with the Exchange SDK.