Skip to main content

C# Catchall Onarrival Event sink

One of the Event sinks that comes in handy from time to time especially if people own a lot of domains is the catch all event sink from http://support.microsoft.com/?kbid=324021 .Now the .NET frameworks a bit more prevalent and the fact I wanted to use this on my Internal servers for something I thought I’d give converting this sink to C# ago. There are two ways you can go about writing SMTP event sinks in managed code the first is to build some wrappers as outlined in on msdn. This gives you access to all the protocol and transport events. The second way to build a SMTP event sink in managed code is to use the CDO onarrival event whose interfaces are defined in the cdoex.dll file (or cdosys.dll if you don’t have Exchange). The downside of using the CDO interface is that it adds significant overhead and is synchronous but the upside is that it that it handles most of the parsing and MIME issues. There’s a good doc here that discusses the issues . But using C# is a step up from VBS and should avoid all those nasty STA issues discussed here.

For the code itself its mostly based on the code from the KB with one major exception. I’ve added a section that checks and sets an X-header on the message if it’s processed by the sink. I did this to make sure the sink wouldn’t run multiple times on a message which was in response to an issue that I had with this sink (as well as the original script) in my environment. What happened for me was that I was a little lazy when I registered the sink and instead of just registering it to run on messages being sent to my catch all domain I registered it to run on all messages that where being processed by the server. This was fine for all normal message traffic that flowed though the server but a problem arose when I had some messages that where bound for a mail enabled public folders. I have a front-backend setup and I have a public store mounted on my front end server which contains the folder hierarchy. So when my front end server received the message bound from a mail-enabled public folder it would deliver it locally first as per its logic and then it would resubmit it once it work out where a replica for that folder existed ref . When the event sink ran on this resubmitted message even though it wasn’t making any changes to it the code still goes though the process of writing the recipient list back to the envelope field and calls datasource.save to update the message. Something was happening within this process which would then cause a message loop on my front-backend servers which would just continually bounce the message between each of the servers until I removed the sink. This may mean I have a problem somewhere else and it wouldn’t have happened if I had bound the sink correctly in the first place but it was enough to prompt me to change this sink to prevent this type of thing happening In the future. The one draw back of adding an X-header was that it invalidates any digital signatures but for a catch domain this isn’t a big deal.

Down to the coding

The first thing to do is create a new classlibrary project in visual studio

To create the sink you need to grab 3 dll’s from your server the first is the codex.dll you also should grab the seo.dll from %windir%\system32\inetsrv and the last dll you need is the PIA for ado. I used the PIA from OWA on Exchange 2003 which is the Adodb.dll file in the Exchsrvr\OMA\browse\bin directory. This is one thing you need to be careful of as there are a few PIA’s kicking around which are different versions. Before you use it you may want to list to see if any are registered in the GAC by using gacutil –l. Usually there isn’t but I had a problem on one server where I had a version of ADODB registered in the GAC which was a different version then the PIA I was trying to use which caused a muck of problems.

Once you have all the DLL’s you need to create strong named assemblies for codex and seo so you need to first create a keypair with sn.exe eg sn.exe –k :SMTPOnarrival.key

Then using Tlbimp.exe build some Interop dll’s I’ve used the namespace switch to make sure CDOEX gets assigned CDO for the namespace eg

tlbimp cdoex.dll /namespace:CDO /keyfile:SMTPOnarrival.key /out:Interop.cdoex.dll

tlbimp seo.dll /namespace:SEO /keyfile:SMTPOnarrival.key /out:Interop.seo.dll

Once you’ve done this you can then reference all three dll’s in your project and you also need to add the keypair name to AssemblyKeyFile property in the assembly info.

The other thing you need to do is in the project properties-configuration properties you need to make sure that Regsiter for Com interop is set to true

Add the code then make sure you add a new unique GUID using Tools – Create GUID (create registry format). You need to change the catch domain and replace mailbox in the code which are hard coded as well you should set a unique x-header for the server.

Once you’ve done this you need to register your dll using regasm with the /codebase switch eg regasm onarrivalesink.dll /codebase

And then finally bind your sink using SMTPreg.vbs (which comes with the Exchange SDK there is also a copy in http://support.microsoft.com/?kbid=324021). When your binding it I would make sure you bind it so it only fires on emails sent to your catch domain recipients so a registration like

cscript smtpreg.vbs /add 1 onarrival CatchallSink SMTPonarrival.Catchall "rcpt to=*@youdomain.com"

If you want to debug your code (which you should only be doing on a dev server) because SMTP event sinks run in-process (of IIS) within Visual Studio to debug you need to select tools – debug process and then attach to the inetinfo.exe process (for CLR). The only quirk that I found was that Inetinfo needs to have successfully loaded your code to allow you to connect the debugger (eg the sink needs to have fired once first) or you just can’t connect. The other small quirk that I haven’t worked out yet is that I had to keep restarting the IISadmin service (and dependants) to make it release the DLL so I could make changes.

The code I’ve used is very low on testing so I wouldn’t trust it in anything other then a test environment.

I’ve put a downloadable copy of the code here

The code itself looks like

using System;
using System.Runtime.InteropServices;
using CDO;
using ADODB;
using SEO;

namespace SMTPonarrival
{
[Guid("E045FD54-4E2D-4a8e-8431-FF351F98B14A")]
public class Catchall : ISMTPOnArrival , IEventIsCacheable
{
void ISMTPOnArrival.OnArrival(IMessage msg, ref CdoEventStatus EventStatus)
{
try
{
if (msg.Fields["urn:schemas:mailheader:X-catchall"].Value == null)
{
ProcessMessage(msg);
};
}
catch(Exception e)
{
System.IO.StreamWriter logfile = new System.IO.StreamWriter("c:\\SMTPEventerrorlog.txt",true);
logfile.WriteLine("Sink Fired : " + System.DateTime.Now);
logfile.WriteLine("Error : " + e.Message);
logfile.Close();
}
//Set Event Status to CDO_RUN_NEXT_SINK
EventStatus = CDO.CdoEventStatus.cdoRunNextSink;
}
void IEventIsCacheable.IsCacheable()
{
// This will return S_OK by default.
}

private void ProcessMessage(IMessage msg1)
{
string RECIPLIST;
RECIPLIST = "http://schemas.microsoft.com/cdo/smtpenvelope/recipientlist";
string strFixedListlc;
string searchdomain = "@catchdomain.com";
string strreplaceaddr = "SMTP:catchmailbox@yourdomain.com;";
string strFixedList = msg1.EnvelopeFields[RECIPLIST].Value.ToString();
while (strFixedList.IndexOf(searchdomain ,1) != -1 )
{
strFixedListlc = strFixedList.ToLower();
int nDomainPart = strFixedListlc.IndexOf(searchdomain,1);
int nNamePart = strFixedList.LastIndexOf(";",nDomainPart);
int nNextAddress = strFixedList.IndexOf("SMTP:",nDomainPart);
if (nNamePart == -1)
{
if (nNextAddress == -1)
{
strFixedList = strreplaceaddr;}
else
{
strFixedList = strreplaceaddr + strFixedList.Remove(0,nNextAddress);}
}
else
{
if (nNextAddress == -1)
{
strFixedList = strFixedList.Remove(nNamePart,strFixedList.Length-nNamePart) + ";" + strreplaceaddr;}
else
{
strFixedList = strFixedList.Remove(nNamePart,strFixedList.Length-nNamePart) + ";" + strreplaceaddr + strFixedList.Remove(0,nNextAddress);
}
}
}
msg1.EnvelopeFields[RECIPLIST].Value = strFixedList;
msg1.EnvelopeFields.Update();
msg1.Fields["urn:schemas:mailheader:X-catchall"].Value = "Server-CatchALL";
msg1.Fields.Update();
msg1.DataSource.Save();
}
}
}

Popular posts from this blog

Export calendar Items to a CSV file using EWS and Powershell

Somebody asked about this last week and while I have a lot of EWS scripts that do access the Calendar I didn't have a simple example that just exported a list of the Calendar events with relevant information to a CSV file so here it is. I've talked on this one before in this howto  but when you query the calendar folder using EWS you need to use a CalendarView which will expand any recurring appointments in a calendar. There are some limits when you use a calendarview in that you can only return a maximum of 2 years of appointments at a time and paging will limit the max number of items to 1000 per call. So if you have a calendar with a very large number of appointments you need to break your query into small date time blocks. In this example script I'm just grabbing the next 7 days of appointments if you want to query a longer period you need to adjust the following lines (keeping in mind what I just mentioned) #Define Date to Query $StartDate = (Get-Date) $EndDate

Downloading a shared file from Onedrive for business using Powershell

I thought I'd quickly share this script I came up with to download a file that was shared using One Drive for Business (which is SharePoint under the covers) with Powershell. The following script takes a OneDrive for business URL which would look like https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filename.txt This script is pretty simple it uses the SharePoint CSOM (Client side object Model) which it loads in the first line. It uses the URI object to separate the host and relative URL which the CSOM requires and also the SharePointOnlineCredentials object to handle the Office365 SharePoint online authentication. The following script is a function that take the OneDrive URL, Credentials for Office365 and path you want to download the file to and downloads the file. eg to run the script you would use something like ./spdownload.ps1 ' https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filena

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.