A walkthrough the code to extend the HP Digital Sending Software

Recently, I wrote about extending the HP Digital Sending Software to “close the gap” and email the OCR’ed result. I received a request to release the code, so I’ve decided to do so with a bit a of documentation.

The HP digital sending software creates an XML document similar to the one below and places it in the same folder with the OCR’ed PDF. This foldername will be set set in the App.config file so this service kniows where to watch.

<--! "s2d96A4D5F15D87FB9658B0ABC53184EE0A_1.xml -->











You can see above, the FileName, From Email Address, To Email Address(es) and Email Subject have been provided. Once this information was married with an SMTP Server, then all of the information required to email the resulting OCR’ed file as an attachment was available.

I wrote and built the code using Visual Studio 2008. Since the code uses .NET 2.0 you could use Visual Studio 2005 or 2008. Alternatively, you can download the full .NET 2.0 SDK and compile the code with the .NET C# compiler, csc.

In order to make the service flexible, the configuration data is in my App.config file




<--! Where HP Scanning Software places the files -->

<--! The file type to watch for changes -->

<--! The smtp server to send the email to -->

<--! The text of the email body -->

<--! What to do after the email "delete" or "keep" -->

<--! If "keep", where to move the processed files -->

<--! The XPATH Query string to locate the from email address inside the HP produced xml file -->

<--! The XPATH Query string to locate the to email address inside the HP produced xml file -->

<--! The XPATH Query string to locate the from subject from inside the HP produced xml file -->



The class inherits from the .NET ServiceBase class

    public class eS_HPScan_WorkFlowEXT_Service : ServiceBase

When the service is started the OnStart method is called; the configuration data is retrieved and the file watcher event handler is setup and activated.

        protected override void OnStart(string[] args)
        {
            log("starting");
            WorkflowScanPath = ConfigurationSettings.AppSettings["WorkFlowScanPath"];
            WorkflowScanProcessedPath = ConfigurationSettings.AppSettings["WorkFlowScanProcessedPath"];
            WorkflowScanFileType = ConfigurationSettings.AppSettings["WorkFlowScanFileType"];
            WorkFlowScanAfterSendAction = ConfigurationSettings.AppSettings["WorkFlowScanAfterSendAction"];
            WorkflowScanSMTPServer = ConfigurationSettings.AppSettings["WorkFlowScanSMTPServer"];
            WorkFlowScanMailText = ConfigurationSettings.AppSettings["WorkFlowScanMailText"];
            WorkFlowScanXPath_From = ConfigurationSettings.AppSettings["WorkFlowScanXPath_From"];
            WorkFlowScanXPath_To = ConfigurationSettings.AppSettings["WorkFlowScanXPath_To"];
            WorkFlowScanXPath_Subject = ConfigurationSettings.AppSettings["WorkFlowScanXPath_Subject"];
            fileSystemWatcher1 = new System.IO.FileSystemWatcher();
            fileSystemWatcher1.Path = WorkflowScanPath;
            fileSystemWatcher1.Filter = WorkflowScanFileType;
            fileSystemWatcher1.IncludeSubdirectories = false;
            fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler(this.File_Changed);
            fileSystemWatcher1.NotifyFilter = (NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName);
            fileSystemWatcher1.EnableRaisingEvents = true;

            log("started" + "-monitoring type(s) " + WorkflowScanFileType + " in folder " + fileSystemWatcher1.Path);
        }

The File_Changed event handler is really pretty boring. All of the real action happens in the method process_file. However, every time a file changes in the folder being watched that is of type *.xml, then this method is called with the full path of the file changed.

        private void File_Changed(object sender, System.IO.FileSystemEventArgs e)
        {
             log("File_Changed: " + e.FullPath);
            // file move causes event on the original file after moved, make sure file exists
            if (System.IO.File.Exists(e.FullPath))
            {
             process_file(e.FullPath);
            }
        }

The process_file method reads the xml file for the attributes of FileName, From Email Address, To Email Address(es) and Email Subject. Afterwards, an email is sent with the OCR’ed file as an attachment. Finally, the files are moved or deleted depending on the setting in the App.config file.

        private void process_file(String xml_file_withpath)
        {
            XmlNode xNode = null;
            XmlDocument xDoc = new XmlDocument();
            Attachment message_attachment = null;
            try
            {
               xDoc.Load(xml_file_withpath);
               xNode = xDoc.SelectSingleNode("/Info");
               string pdf_File_name = xNode.Attributes.GetNamedItem("FileName").Value; 
               pdf_File_name = WorkflowScanPath + pathSep + pdf_File_name;
               string scan_from = get_email_info(xDoc, WorkFlowScanXPath_From);
               string scan_to = get_email_info(xDoc, WorkFlowScanXPath_To);
               string scan_subject = get_email_info(xDoc, WorkFlowScanXPath_Subject);
                MailMessage message = new MailMessage();
               message.From = new MailAddress(scan_from);
               // allow the to address to use , ; and whitespace as separators
               if (scan_to.Contains(",") || scan_to.Contains(";") || scan_to.Contains(" "))
               {
                   string[] stringSeparators = new string[] { ",", ";", " " };
                   string[] to_addresses = scan_to.Split(stringSeparators, StringSplitOptions.None);
                   foreach (string ts in to_addresses)
                   {
                       if (ts != null && !ts.Equals("") && !ts.Equals(" "))
                       {
                           message.To.Add(new MailAddress(ts));
                       }
                   }
               }
               else
               {
                   message.To.Add(new MailAddress(scan_to));
               }

               message.Subject = scan_subject;
               message.Body = WorkFlowScanMailText;
               // Create  the file attachment for this e-mail message.
               message_attachment = attach_file(pdf_File_name);
               // Add the file attachment to this e-mail message.
               message.Attachments.Add(message_attachment);
               //Send the message.
               SmtpClient mclient = new SmtpClient(WorkflowScanSMTPServer);
               mclient.Send(message);
               message_attachment.Dispose(); // free the memory and files!!
               message_attachment = null;
               move_delete_file(xml_file_withpath);
               move_delete_file(pdf_File_name);
            }
            catch (Exception ex)
            {
                log("Exception caught in process_file(): " + ex.ToString(), EventLogEntryType.Error);  
            }
            if (message_attachment != null)
            {
                message_attachment.Dispose(); // free the memory!!
            }
        }

The attach_file method deserves a bit of explanation. In a few cases, the XML file arrives before the actual OCR’ed file. To handle this case there is a loop to make sure that the OCR’ed file exists.

        private Attachment attach_file(string attach_file)
        {
            Attachment result = null;
            int cmax = 10;
            // Try file for 10 seconds
            for (int i=1;i <= cmax; i++)
            {
                if (!System.IO.File.Exists(attach_file))
                {
                    if (i == cmax)
                    {
                        throw new Exception("File does not exist: " + attach_file);
                    }
                    System.Threading.Thread.Sleep(1000);
                }
                else
                {
                    break;
                }
            }

            result = new Attachment(attach_file, MediaTypeNames.Application.Octet);
            // Add time stamp information for the file.
            ContentDisposition disposition = result.ContentDisposition;
            disposition.CreationDate = System.IO.File.GetCreationTime(attach_file);
            disposition.ModificationDate = System.IO.File.GetLastWriteTime(attach_file);
            disposition.ReadDate = System.IO.File.GetLastAccessTime(attach_file);
            return result;
        }

I think the remainder of the methods are reliatively straight forward and unless someone wants an explanation I'll leave that as an exercise for the reader. I did note that one improvement could be to place the email address separators in the App.config file. Although ";", "," and " " are the ones commonly used in email.

The full code listing.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Net.Mail;
using System.Net.Mime;
using System.ServiceProcess;
using System.Text;
using System.Xml;
using System.Xml.XPath;

using eS_HPScan_Workflow_Extender;

namespace eS_HPScan_Workflow_Extender
{
    public class eS_HPScan_WorkFlowEXT_Service : ServiceBase
    {
        private System.ComponentModel.Container components = null;
        private System.IO.FileSystemWatcher fileSystemWatcher1 = null;
        private String WorkflowScanPath = null;
        private String WorkFlowScanAfterSendAction = null;
        private String WorkflowScanProcessedPath = null;
        private String WorkflowScanFileType = null;
        private String WorkflowScanSMTPServer = null;
        private String WorkFlowScanMailText = null;
        private String WorkFlowScanXPath_From = null;
        private String WorkFlowScanXPath_To = null;
        private String WorkFlowScanXPath_Subject = null;
        private static String pathSep = "\\";
        private static String delete_action = "delete";
        private static int default_EventID = 0;

	public eS_HPScan_WorkFlowEXT_Service()
	{
		// This call is required by the Windows.Forms Component Designer.
		InitializeComponent();
		// TODO: Add any initialization after the InitComponent call
	}

	// The main entry point for the process
	static void Main()
	{
		System.ServiceProcess.ServiceBase[] ServicesToRun;	
		ServicesToRun = new System.ServiceProcess.ServiceBase[] { new eS_HPScan_WorkFlowEXT_Service() };
		System.ServiceProcess.ServiceBase.Run(ServicesToRun);
		}

		///  
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// 
	private void InitializeComponent()
	{
		// 
		// eS_HPScan_WorkFlowEXT_Service
		// 
		this.CanShutdown = true;
		this.ServiceName = "eS_HPScan_WorkFlowEXT_Service";
	}
	/// 
	/// Clean up any resources being used.
	/// 
	protected override void Dispose( bool disposing )
	{
		if( disposing )
		{
			if (components != null) 
			{
				components.Dispose();
			}
		}
		base.Dispose( disposing );
	}


        protected override void OnStart(string[] args)
        {
            log("starting");
            WorkflowScanPath = ConfigurationSettings.AppSettings["WorkFlowScanPath"];
            WorkflowScanProcessedPath = ConfigurationSettings.AppSettings["WorkFlowScanProcessedPath"];
            WorkflowScanFileType = ConfigurationSettings.AppSettings["WorkFlowScanFileType"];
            WorkFlowScanAfterSendAction = ConfigurationSettings.AppSettings["WorkFlowScanAfterSendAction"];
            WorkflowScanSMTPServer = ConfigurationSettings.AppSettings["WorkFlowScanSMTPServer"];
            WorkFlowScanMailText = ConfigurationSettings.AppSettings["WorkFlowScanMailText"];
            WorkFlowScanXPath_From = ConfigurationSettings.AppSettings["WorkFlowScanXPath_From"];
            WorkFlowScanXPath_To = ConfigurationSettings.AppSettings["WorkFlowScanXPath_To"];
            WorkFlowScanXPath_Subject = ConfigurationSettings.AppSettings["WorkFlowScanXPath_Subject"];

            fileSystemWatcher1 = new System.IO.FileSystemWatcher();
            fileSystemWatcher1.Path = WorkflowScanPath;
            fileSystemWatcher1.Filter = WorkflowScanFileType;
            fileSystemWatcher1.IncludeSubdirectories = false;
            fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler(this.File_Changed);
            fileSystemWatcher1.NotifyFilter = (NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName);
            fileSystemWatcher1.EnableRaisingEvents = true;

            log("started" + "-monitoring type(s) " + WorkflowScanFileType + " in folder " + fileSystemWatcher1.Path);
        }

        protected override void OnStop()
        {
            log("stopping");
            log("stopped");
        }

        private void File_Changed(object sender, System.IO.FileSystemEventArgs e)
        {
             log("File_Changed: " + e.FullPath);
            // file move causes event on the original file after moved, make sure file exists
            if (System.IO.File.Exists(e.FullPath))
            {
             process_file(e.FullPath);
            }
        }

        private void process_file(String xml_file_withpath)
        {
            XmlNode xNode = null;
            XmlDocument xDoc = new XmlDocument();
            Attachment message_attachment = null;
            try
            {
               xDoc.Load(xml_file_withpath);
               xNode = xDoc.SelectSingleNode("/Info");
               string pdf_File_name = xNode.Attributes.GetNamedItem("FileName").Value; 
               pdf_File_name = WorkflowScanPath + pathSep + pdf_File_name;

               string scan_from = get_email_info(xDoc, WorkFlowScanXPath_From);
               string scan_to = get_email_info(xDoc, WorkFlowScanXPath_To);
               string scan_subject = get_email_info(xDoc, WorkFlowScanXPath_Subject);
                MailMessage message = new MailMessage();
               message.From = new MailAddress(scan_from);
               // allow the to address to use , ; and whitespace as separators
               if (scan_to.Contains(",") || scan_to.Contains(";") || scan_to.Contains(" "))
               {
                   string[] stringSeparators = new string[] { ",", ";", " " };
                   string[] to_addresses = scan_to.Split(stringSeparators, StringSplitOptions.None);
                   foreach (string ts in to_addresses)
                   {
                       if (ts != null && !ts.Equals("") && !ts.Equals(" "))
                       {
                           message.To.Add(new MailAddress(ts));
                       }
                   }
               }
               else
               {
                   message.To.Add(new MailAddress(scan_to));
               }

               message.Subject = scan_subject;
               message.Body = WorkFlowScanMailText; 
               // Create  the file attachment for this e-mail message.
               message_attachment = attach_file(pdf_File_name);
               // Add the file attachment to this e-mail message.
               message.Attachments.Add(message_attachment);
               //Send the message.
               SmtpClient mclient = new SmtpClient(WorkflowScanSMTPServer);
               mclient.Send(message);
               message_attachment.Dispose(); // free the memory and files!!
               message_attachment = null;
               move_delete_file(xml_file_withpath);
               move_delete_file(pdf_File_name);
            }
            catch (Exception ex)
            {
                log("Exception caught in process_file(): " + ex.ToString(), EventLogEntryType.Error);  
            }
            if (message_attachment != null)
            {
                message_attachment.Dispose(); // free the memory!!
            }
        }

        private Attachment attach_file(string attach_file)
        {
            Attachment result = null;
            int cmax = 10;
            // Try file for 10 seconds
            for (int i=1;i <= cmax; i++)
            {
                if (!System.IO.File.Exists(attach_file))
                {
                    if (i == cmax)
                    {
                        throw new Exception("File does not exist: " + attach_file);
                    }
                    System.Threading.Thread.Sleep(1000);
                }
                else
                {
                    break;
                }
            }

            result = new Attachment(attach_file, MediaTypeNames.Application.Octet);
            // Add time stamp information for the file.
            ContentDisposition disposition = result.ContentDisposition;
            disposition.CreationDate = System.IO.File.GetCreationTime(attach_file);
            disposition.ModificationDate = System.IO.File.GetLastWriteTime(attach_file);
            disposition.ReadDate = System.IO.File.GetLastAccessTime(attach_file);
            return result;

        }

        private void move_delete_file(string file1)
        {
            try
            {
                string destfile = WorkflowScanProcessedPath + pathSep + System.IO.Path.GetFileName(file1);
                if (WorkFlowScanAfterSendAction.ToLower().Equals(delete_action))
                {
                    System.IO.File.Delete(file1);
                }
                else
                {
                    // Move does not have over write option
                    if (System.IO.File.Exists(destfile))
                    {
                        System.IO.File.Delete(destfile);
                    }
                    System.IO.File.Move(file1, destfile);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Source: " + file1 + " - Dest: " + WorkflowScanProcessedPath + " ",ex);
            }
        }
        private string get_email_info(XmlDocument xd, string p)
        {
            string result = null;
            XmlNode xn = xd.SelectSingleNode(p);
            XmlNode cxn = xn.ChildNodes[0];
            if (cxn is XmlCDataSection)
            {
                XmlCDataSection cdataSection = cxn as XmlCDataSection;
                result = cdataSection.Value;
            }
            return result;
        }

        public void log(String s)
        {
            log(s, EventLogEntryType.Information, default_EventID);
        } // end method

        public void log(String s, EventLogEntryType logtype)
        {
            log(s, logtype, default_EventID);
        } // end method

        public void log(String s, EventLogEntryType logtype, int EventID)
        {
            EventLog elog = getmylog();
            elog.WriteEntry(this.ServiceName + " " + s, logtype, EventID);
        } // end method

        private EventLog getmylog()
        {
            EventLog elog;
            String eventsource, logName;
            //This name gets registered in the windows registry in
            //a place where the event log knows where to find it.
            eventsource = this.ServiceName;
            //Give the event log a unique name. Don't choose 
            //Application, Security or System as a name
            logName = "Application";
            if (!EventLog.SourceExists(eventsource))
            {
                EventLog.CreateEventSource(eventsource, logName);
            }
            elog = new EventLog();
            //set the log to read and write from to the new log
            elog.Log = logName;
            //set the source to this program
            elog.Source = eventsource;
            return elog;
        }
    }
}
Be Sociable, Share!
This entry was posted in .NET and tagged , , , , . Bookmark the permalink.