Skip to main content

Syslogging the Message Tracking Logs on Exchange 2x

The Syslog protocol is a very popular way of sending log information over TCP/IP to event log collectors (The introduction to Syslog in the RFC is well worth a read).Sendmail can do logging to a syslog server straight of the bat but in the Windows world it unfortunately doesn’t get much of a run. Exchange does make its tracking logs available via WMI as well as the raw tab separated text file but doesn’t give you any way of sending them to another server. To get my Exchange server producing syslog messages for the tracking log data I thought I’d write a small c# console application that would retrieve the tracking log data for the last 5 minutes then create and send syslog messages for each message in the log. Then all I need to do is create a scheduled task on the Exchange boxes to run the app every five minutes.

Digging a bit deeper.

When you dig a bit deeper into the message tracking logs on Exchange you’ll find there are a lot of events that are logged and only some of them are of day to day use. The way I approached the application is that I decided I wanted to log 3 different types of syslog events the first was a Mail.Notice priority syslog message for normal email traffic on the server. I also run the IMF on my exchange boxes so I wanted to send a different Syslog message for any messages that where blocked by the IMF on my server so I decided to use the Mail.Warning priority for any IMF messages. The last event I wanted to know about was any NDR’s that where sent so I used the Mail.Error priority syslog for these. There where a couple of approaches I could have taken to extract these messages from the log files the first would have been just to open the file and parse it out. The second was to query every event for the last five minutes via WMI and then filter out the ones I didn’t want. The third method was to query the logs for each of the different event numbers and use the filtered result set that WMI returns. I went for the third method which means that the code ends up doing three separate queries to get all the different categories of the events I wanted to report on. This didn’t seem to come at two much of a performance penalty and made the code a little more logical to understand. I may switch to another method once I got some more testing done. (I guess a fourth method would have been to use the Microsoft log parser util)

The Code itself

The code itself consists of two functions the first function goes out and grabs all the tracking log events for the last five minutes based on two input parameters which are the eventids and servername and then builds a syslog message for each log entry and then stores the message text in an arraylist which is returned to the calling stub. The main stub then goes though the arraylist and calls the sendsyslog function which takes the syslog message text, the priority of the Syslog message you want to send and the ipaddress of the syslog server you want to send to. The Syslog sending code itself is pretty simple the following links where helping in putting something basic together and . I should make one note about the WMI code when you have a message that is sent to multiple recipients WMI returns this to you as one entry. I guess it all depends on the way you look at it but a message sent to two recipients to me is two messages. So what I’ve done with the code is I create a separate syslog event for every recipient of a message. An alternative to this would be to create one syslog message and then stack the to field this reduces the number of syslog messages but makes it harder to process.

The code is designed to run with two command line parameters the first is the name of the Exchange server you want to extract the data from and the second is the IPaddress of the syslog server you want the syslogs sent to.

Making it real-time

Having this run in real-time vs on a scheduled basis is certainly a more desirable way to go. There is an Event that gets fired "OnMsgTrackLog" which looks like it could be used to hook in and do this with but its undocumented territory so I’ve had to leave untill I have more time.

I’ve put a downloadable copy of the code here the code itself looks like

using System;
using System.Collections;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.Management;

namespace ExchangeTrackinglogSender
class SendLog
static void Main(string[] args)
SendLog sl = new SendLog();
string snServername = args[0];
string snSyslogIPName = args[1];
ArrayList alEmailalist = sl.getRecentLogs(5,snServername,1020,1028);
IEnumerator raEnumerator = alEmailalist.GetEnumerator();
while ( raEnumerator.MoveNext() )
ArrayList alIMFalist = sl.getRecentLogs(5,snServername,1039,1039);
IEnumerator raEnumerator1 = alIMFalist.GetEnumerator();
while ( raEnumerator1.MoveNext() )
ArrayList alNDRalist = sl.getRecentLogs(5,snServername,1026,1030);
IEnumerator raEnumerator2 = alNDRalist.GetEnumerator();
while ( raEnumerator2.MoveNext() )
catch(IndexOutOfRangeException e){
Console.WriteLine("Usage exsyslog.exe ");
catch(Exception e)
System.IO.StreamWriter elErrorlog = new System.IO.StreamWriter("c:\\Syslogsendererror.log",true);
elErrorlog.WriteLine(System.DateTime.Now + " " + e.InnerException + " " + " Exception Description:" + e.ToString());

void sendsyslog(string mtMessagetxt,string iaIpaddress, int spPriority){
UdpClient ucUdpclient = new UdpClient(iaIpaddress, 514);
byte[] rawMsg;
string strParams = System.String.Format("<{0}>{1}",spPriority, mtMessagetxt);
rawMsg = System.Text.Encoding.ASCII.GetBytes(string.Concat(strParams));
ucUdpclient.Send(rawMsg, rawMsg.Length);
ArrayList getRecentLogs(int tiTimeinterval,string snServername, int idTid1, int idTid2){
ArrayList alRecpalist = new ArrayList();
ConnectionOptions coObjconn = new ConnectionOptions();
coObjconn.Impersonation = ImpersonationLevel.Impersonate;
coObjconn.EnablePrivileges = true;
string qtQuerytime = ManagementDateTimeConverter.ToDmtfDateTime(DateTime.Now.ToUniversalTime().AddMinutes(-tiTimeinterval));
ManagementScope msExmangescope = new ManagementScope(@"\\" + snServername + @"\root\MicrosoftExchangeV2",coObjconn);
ObjectQuery qoObjquery = new ObjectQuery("Select * FROM Exchange_MessageTrackingEntry where entrytype = '" + idTid1 + "' and OriginationTime >= '" + qtQuerytime + "' or entrytype = '" + idTid2 + "' and OriginationTime > '" + qtQuerytime + "'");
System.Management.ManagementObjectSearcher osObjsearch = new System.Management.ManagementObjectSearcher(msExmangescope,qoObjquery);
System.Management.ManagementObjectCollection qcQueryCollection1 = osObjsearch.Get();
foreach( System.Management.ManagementObject reTrackinglogEntry in qcQueryCollection1 )
string[] raReciparray = (string[])reTrackinglogEntry["RecipientAddress"];
UInt32[] raRecipStatusarray = (UInt32[])reTrackinglogEntry["RecipientStatus"];
for(int i=0;i<=raReciparray.GetUpperBound(0);i++)
string msMsgtext;
msMsgtext = "date=" + ManagementDateTimeConverter.ToDateTime(reTrackinglogEntry["OriginationTime"].ToString()).ToString("yyyy-MM-dd") + ",";
msMsgtext = msMsgtext + "time=" + ManagementDateTimeConverter.ToDateTime(reTrackinglogEntry["OriginationTime"].ToString()).ToString("HH:mm:ss") + ",";
msMsgtext = msMsgtext + "servername=" + snServername + ",eventid=" + reTrackinglogEntry["EntryType"].ToString() + ",";
msMsgtext = msMsgtext + "msgid=" + "\"" + reTrackinglogEntry["MessageID"].ToString() + "\",RecipientType=" + raRecipStatusarray[i] + ",";
msMsgtext = msMsgtext + "nrcpts=" + reTrackinglogEntry["RecipientCount"] + ",";
if(reTrackinglogEntry["subject"] != null) {msMsgtext = msMsgtext + "subject=\"" + reTrackinglogEntry["subject"].ToString().Replace("\"","") + "\",";}
msMsgtext = msMsgtext + "from=" + reTrackinglogEntry["SenderAddress"].ToString() + ",to=" + raReciparray[i] + ",size=" + reTrackinglogEntry["size"].ToString();
return alRecpalist;


Popular posts from this blog

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 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 '

A walk-though using the Graph API Mailbox reports in Powershell

Quite recently the Reporting side of the Graph API has moved in GA from beta, there are quite a number of reports that can be run across various Office365 surfaces but in this post I'm going to focus on the Mailbox related ones. Accessing Office365 Reports using Powershell is nothing new and has been available in the previous reporting endpoint however from the end of January many of these cmdlets are now being depreciated in favour of the Graph API . Prerequisites  In comparison to using the Remote PowerShell cmdlets where only the correct Office365 Admin permissions where needed, to use the new Graph API reports endpoint you need to use OAuth for authentication so this requires an Application Registration  that is then given the correct oAuth Grants to use the Reports EndPoin

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th
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.