David's technobabble Rotating Header Image

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.

  1.  
  2. <–! "s2d96A4D5F15D87FB9658B0ABC53184EE0A_1.xml –>
  3. <?xml version="1.0" encoding="UTF-8"?>
  4. <Info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="4.0" SentTimeStamp="2009-05-17 01:59:15 Pacific Daylight Time (GMT+08:00)" FileName="s2d96A4D5F15D87FB9658B0ABC53184EE0A_1.pdf">
  5. <Scanner Name="my-printer" ModelName="hp LaserJet 4345 mfp" Status="0"></Scanner>
  6. <Sender Name="GUEST"></Sender>
  7. <ScanSettings FileFormat="jpeg" SendingQuality="COLOR DOCUMENT" Pages="1" Duplex="0"></ScanSettings>
  8. <Destination Name="SEND PDF TO MAIL" Path="*.COMMON MFP GROUP.SEARCHABLE PDF" NumberOfPrompts="3">
  9. <Prompt Index="0" Name="MY-FROM" Displaytext="From:"><![CDATA[printer@somedomain]]></Prompt>
  10. <Prompt Index="1" Name="MY-TO" Displaytext="To:"><![CDATA[email@somedomain]]></Prompt>
  11. <Prompt Index="2" Name="MY-SUBJECT" Displaytext="Subject:"><![CDATA[Digital Document from My Printer]]></Prompt>
  12. </Destination>
  13. </Info>
  14.  

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

  1.  
  2. <?xml version="1.0" encoding="utf-8" ?>
  3. <configuration>
  4. <appSettings>
  5. <–! Where HP Scanning Software places the files –>
  6. <add key="WorkFlowScanPath" value="D:\test\files" />
  7. <–! The file type to watch for changes –>
  8. <add key="WorkFlowScanFileType" value="*.xml" />
  9. <–! The smtp server to send the email to –>
  10. <add key="WorkFlowScanSMTPServer" value="some.smtpserver" />
  11. <–! The text of the email body –>
  12. <add key="WorkFlowScanMailText" value=’Your scanned document is attached.’ />
  13. <–! What to do after the email "delete" or "keep" –>
  14. <add key="WorkFlowScanAfterSendAction" value="delete" />
  15. <–! If "keep", where to move the processed files –>
  16. <add key="WorkFlowScanProcessedPath" value="D:\test\processed" />
  17. <–! The XPATH Query string to locate the from email address inside the HP produced xml file –>
  18. <add key="WorkFlowScanXPath_From" value=’/Info/Destination/Prompt[@Displaytext="From:"]‘ />
  19. <–! The XPATH Query string to locate the to email address inside the HP produced xml file –>
  20. <add key="WorkFlowScanXPath_To" value=’/Info/Destination/Prompt[@Displaytext="To:"]‘ />
  21. <–! The XPATH Query string to locate the from subject from inside the HP produced xml file –>
  22. <add key="WorkFlowScanXPath_Subject" value=’/Info/Destination/Prompt[@Displaytext="Subject:"]‘ />
  23. </appSettings>
  24. </configuration>
  25.  

The class inherits from the .NET ServiceBase class

  1.  
  2.     public class eS_HPScan_WorkFlowEXT_Service : ServiceBase
  3.  

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.

  1.  
  2.         protected override void OnStart(string[] args)
  3.         {
  4.             log("starting");
  5.             WorkflowScanPath = ConfigurationSettings.AppSettings["WorkFlowScanPath"];
  6.             WorkflowScanProcessedPath = ConfigurationSettings.AppSettings["WorkFlowScanProcessedPath"];
  7.             WorkflowScanFileType = ConfigurationSettings.AppSettings["WorkFlowScanFileType"];
  8.             WorkFlowScanAfterSendAction = ConfigurationSettings.AppSettings["WorkFlowScanAfterSendAction"];
  9.             WorkflowScanSMTPServer = ConfigurationSettings.AppSettings["WorkFlowScanSMTPServer"];
  10.             WorkFlowScanMailText = ConfigurationSettings.AppSettings["WorkFlowScanMailText"];
  11.             WorkFlowScanXPath_From = ConfigurationSettings.AppSettings["WorkFlowScanXPath_From"];
  12.             WorkFlowScanXPath_To = ConfigurationSettings.AppSettings["WorkFlowScanXPath_To"];
  13.             WorkFlowScanXPath_Subject = ConfigurationSettings.AppSettings["WorkFlowScanXPath_Subject"];
  14.             fileSystemWatcher1 = new System.IO.FileSystemWatcher();
  15.             fileSystemWatcher1.Path = WorkflowScanPath;
  16.             fileSystemWatcher1.Filter = WorkflowScanFileType;
  17.             fileSystemWatcher1.IncludeSubdirectories = false;
  18.             fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler(this.File_Changed);
  19.             fileSystemWatcher1.NotifyFilter = (NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName);
  20.             fileSystemWatcher1.EnableRaisingEvents = true;
  21.  
  22.             log("started" + "-monitoring type(s) " + WorkflowScanFileType + " in folder " + fileSystemWatcher1.Path);
  23.         }
  24.  

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.

  1.  
  2.         private void File_Changed(object sender, System.IO.FileSystemEventArgs e)
  3.         {
  4.              log("File_Changed: " + e.FullPath);
  5.             // file move causes event on the original file after moved, make sure file exists
  6.             if (System.IO.File.Exists(e.FullPath))
  7.             {
  8.              process_file(e.FullPath);
  9.             }
  10.         }
  11.  

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.

  1.  
  2.         private void process_file(String xml_file_withpath)
  3.         {
  4.             XmlNode xNode = null;
  5.             XmlDocument xDoc = new XmlDocument();
  6.             Attachment message_attachment = null;
  7.             try
  8.             {
  9.                xDoc.Load(xml_file_withpath);
  10.                xNode = xDoc.SelectSingleNode("/Info");
  11.                string pdf_File_name = xNode.Attributes.GetNamedItem("FileName").Value;
  12.                pdf_File_name = WorkflowScanPath + pathSep + pdf_File_name;
  13.                string scan_from = get_email_info(xDoc, WorkFlowScanXPath_From);
  14.                string scan_to = get_email_info(xDoc, WorkFlowScanXPath_To);
  15.                string scan_subject = get_email_info(xDoc, WorkFlowScanXPath_Subject);
  16.                 MailMessage message = new MailMessage();
  17.                message.From = new MailAddress(scan_from);
  18.                // allow the to address to use , ; and whitespace as separators
  19.                if (scan_to.Contains(",") || scan_to.Contains(";") || scan_to.Contains(" "))
  20.                {
  21.                    string[] stringSeparators = new string[] { ",", ";", " " };
  22.                    string[] to_addresses = scan_to.Split(stringSeparators, StringSplitOptions.None);
  23.                    foreach (string ts in to_addresses)
  24.                    {
  25.                        if (ts != null && !ts.Equals("") && !ts.Equals(" "))
  26.                        {
  27.                            message.To.Add(new MailAddress(ts));
  28.                        }
  29.                    }
  30.                }
  31.                else
  32.                {
  33.                    message.To.Add(new MailAddress(scan_to));
  34.                }
  35.  
  36.                message.Subject = scan_subject;
  37.                message.Body = WorkFlowScanMailText;
  38.                // Create  the file attachment for this e-mail message.
  39.                message_attachment = attach_file(pdf_File_name);
  40.                // Add the file attachment to this e-mail message.
  41.                message.Attachments.Add(message_attachment);
  42.                //Send the message.
  43.                SmtpClient mclient = new SmtpClient(WorkflowScanSMTPServer);
  44.                mclient.Send(message);
  45.                message_attachment.Dispose(); // free the memory and files!!
  46.                message_attachment = null;
  47.                move_delete_file(xml_file_withpath);
  48.                move_delete_file(pdf_File_name);
  49.             }
  50.             catch (Exception ex)
  51.             {
  52.                 log("Exception caught in process_file(): " + ex.ToString(), EventLogEntryType.Error);  
  53.             }
  54.             if (message_attachment != null)
  55.             {
  56.                 message_attachment.Dispose(); // free the memory!!
  57.             }
  58.         }
  59.  

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.

  1.  
  2.         private Attachment attach_file(string attach_file)
  3.         {
  4.             Attachment result = null;
  5.             int cmax = 10;
  6.             // Try file for 10 seconds
  7.             for (int i=1;i <= cmax; i++)
  8.             {
  9.                 if (!System.IO.File.Exists(attach_file))
  10.                 {
  11.                     if (i == cmax)
  12.                     {
  13.                         throw new Exception("File does not exist: " + attach_file);
  14.                     }
  15.                     System.Threading.Thread.Sleep(1000);
  16.                 }
  17.                 else
  18.                 {
  19.                     break;
  20.                 }
  21.             }
  22.  
  23.             result = new Attachment(attach_file, MediaTypeNames.Application.Octet);
  24.             // Add time stamp information for the file.
  25.             ContentDisposition disposition = result.ContentDisposition;
  26.             disposition.CreationDate = System.IO.File.GetCreationTime(attach_file);
  27.             disposition.ModificationDate = System.IO.File.GetLastWriteTime(attach_file);
  28.             disposition.ReadDate = System.IO.File.GetLastAccessTime(attach_file);
  29.             return result;
  30.         }
  31.  

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.

  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Configuration;
  6. using System.Data;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Net.Mail;
  10. using System.Net.Mime;
  11. using System.ServiceProcess;
  12. using System.Text;
  13. using System.Xml;
  14. using System.Xml.XPath;
  15.  
  16. using eS_HPScan_Workflow_Extender;
  17.  
  18. namespace eS_HPScan_Workflow_Extender
  19. {
  20.     public class eS_HPScan_WorkFlowEXT_Service : ServiceBase
  21.     {
  22.         private System.ComponentModel.Container components = null;
  23.         private System.IO.FileSystemWatcher fileSystemWatcher1 = null;
  24.         private String WorkflowScanPath = null;
  25.         private String WorkFlowScanAfterSendAction = null;
  26.         private String WorkflowScanProcessedPath = null;
  27.         private String WorkflowScanFileType = null;
  28.         private String WorkflowScanSMTPServer = null;
  29.         private String WorkFlowScanMailText = null;
  30.         private String WorkFlowScanXPath_From = null;
  31.         private String WorkFlowScanXPath_To = null;
  32.         private String WorkFlowScanXPath_Subject = null;
  33.         private static String pathSep = "\\";
  34.         private static String delete_action = "delete";
  35.         private static int default_EventID = 0;
  36.  
  37.         public eS_HPScan_WorkFlowEXT_Service()
  38.         {
  39.                 // This call is required by the Windows.Forms Component Designer.
  40.                 InitializeComponent();
  41.                 // TODO: Add any initialization after the InitComponent call
  42.         }
  43.  
  44.         // The main entry point for the process
  45.         static void Main()
  46.         {
  47.                 System.ServiceProcess.ServiceBase[] ServicesToRun;     
  48.                 ServicesToRun = new System.ServiceProcess.ServiceBase[] { new eS_HPScan_WorkFlowEXT_Service() };
  49.                 System.ServiceProcess.ServiceBase.Run(ServicesToRun);
  50.                 }
  51.  
  52.                 /// <summary>
  53.                 /// Required method for Designer support – do not modify
  54.                 /// the contents of this method with the code editor.
  55.                 /// </summary>
  56.         private void InitializeComponent()
  57.         {
  58.                 //
  59.                 // eS_HPScan_WorkFlowEXT_Service
  60.                 //
  61.                 this.CanShutdown = true;
  62.                 this.ServiceName = "eS_HPScan_WorkFlowEXT_Service";
  63.         }
  64.         /// <summary>
  65.         /// Clean up any resources being used.
  66.         /// </summary>
  67.         protected override void Dispose( bool disposing )
  68.         {
  69.                 if( disposing )
  70.                 {
  71.                         if (components != null)
  72.                         {
  73.                                 components.Dispose();
  74.                         }
  75.                 }
  76.                 base.Dispose( disposing );
  77.         }
  78.  
  79.  
  80.         protected override void OnStart(string[] args)
  81.         {
  82.             log("starting");
  83.             WorkflowScanPath = ConfigurationSettings.AppSettings["WorkFlowScanPath"];
  84.             WorkflowScanProcessedPath = ConfigurationSettings.AppSettings["WorkFlowScanProcessedPath"];
  85.             WorkflowScanFileType = ConfigurationSettings.AppSettings["WorkFlowScanFileType"];
  86.             WorkFlowScanAfterSendAction = ConfigurationSettings.AppSettings["WorkFlowScanAfterSendAction"];
  87.             WorkflowScanSMTPServer = ConfigurationSettings.AppSettings["WorkFlowScanSMTPServer"];
  88.             WorkFlowScanMailText = ConfigurationSettings.AppSettings["WorkFlowScanMailText"];
  89.             WorkFlowScanXPath_From = ConfigurationSettings.AppSettings["WorkFlowScanXPath_From"];
  90.             WorkFlowScanXPath_To = ConfigurationSettings.AppSettings["WorkFlowScanXPath_To"];
  91.             WorkFlowScanXPath_Subject = ConfigurationSettings.AppSettings["WorkFlowScanXPath_Subject"];
  92.  
  93.             fileSystemWatcher1 = new System.IO.FileSystemWatcher();
  94.             fileSystemWatcher1.Path = WorkflowScanPath;
  95.             fileSystemWatcher1.Filter = WorkflowScanFileType;
  96.             fileSystemWatcher1.IncludeSubdirectories = false;
  97.             fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler(this.File_Changed);
  98.             fileSystemWatcher1.NotifyFilter = (NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName);
  99.             fileSystemWatcher1.EnableRaisingEvents = true;
  100.  
  101.             log("started" + "-monitoring type(s) " + WorkflowScanFileType + " in folder " + fileSystemWatcher1.Path);
  102.         }
  103.  
  104.         protected override void OnStop()
  105.         {
  106.             log("stopping");
  107.             log("stopped");
  108.         }
  109.  
  110.         private void File_Changed(object sender, System.IO.FileSystemEventArgs e)
  111.         {
  112.              log("File_Changed: " + e.FullPath);
  113.             // file move causes event on the original file after moved, make sure file exists
  114.             if (System.IO.File.Exists(e.FullPath))
  115.             {
  116.              process_file(e.FullPath);
  117.             }
  118.         }
  119.  
  120.         private void process_file(String xml_file_withpath)
  121.         {
  122.             XmlNode xNode = null;
  123.             XmlDocument xDoc = new XmlDocument();
  124.             Attachment message_attachment = null;
  125.             try
  126.             {
  127.                xDoc.Load(xml_file_withpath);
  128.                xNode = xDoc.SelectSingleNode("/Info");
  129.                string pdf_File_name = xNode.Attributes.GetNamedItem("FileName").Value;
  130.                pdf_File_name = WorkflowScanPath + pathSep + pdf_File_name;
  131.  
  132.                string scan_from = get_email_info(xDoc, WorkFlowScanXPath_From);
  133.                string scan_to = get_email_info(xDoc, WorkFlowScanXPath_To);
  134.                string scan_subject = get_email_info(xDoc, WorkFlowScanXPath_Subject);
  135.                 MailMessage message = new MailMessage();
  136.                message.From = new MailAddress(scan_from);
  137.                // allow the to address to use , ; and whitespace as separators
  138.                if (scan_to.Contains(",") || scan_to.Contains(";") || scan_to.Contains(" "))
  139.                {
  140.                    string[] stringSeparators = new string[] { ",", ";", " " };
  141.                    string[] to_addresses = scan_to.Split(stringSeparators, StringSplitOptions.None);
  142.                    foreach (string ts in to_addresses)
  143.                    {
  144.                        if (ts != null && !ts.Equals("") && !ts.Equals(" "))
  145.                        {
  146.                            message.To.Add(new MailAddress(ts));
  147.                        }
  148.                    }
  149.                }
  150.                else
  151.                {
  152.                    message.To.Add(new MailAddress(scan_to));
  153.                }
  154.  
  155.                message.Subject = scan_subject;
  156.                message.Body = WorkFlowScanMailText;
  157.                // Create  the file attachment for this e-mail message.
  158.                message_attachment = attach_file(pdf_File_name);
  159.                // Add the file attachment to this e-mail message.
  160.                message.Attachments.Add(message_attachment);
  161.                //Send the message.
  162.                SmtpClient mclient = new SmtpClient(WorkflowScanSMTPServer);
  163.                mclient.Send(message);
  164.                message_attachment.Dispose(); // free the memory and files!!
  165.                message_attachment = null;
  166.                move_delete_file(xml_file_withpath);
  167.                move_delete_file(pdf_File_name);
  168.             }
  169.             catch (Exception ex)
  170.             {
  171.                 log("Exception caught in process_file(): " + ex.ToString(), EventLogEntryType.Error);  
  172.             }
  173.             if (message_attachment != null)
  174.             {
  175.                 message_attachment.Dispose(); // free the memory!!
  176.             }
  177.         }
  178.  
  179.         private Attachment attach_file(string attach_file)
  180.         {
  181.             Attachment result = null;
  182.             int cmax = 10;
  183.             // Try file for 10 seconds
  184.             for (int i=1;i <= cmax; i++)
  185.             {
  186.                 if (!System.IO.File.Exists(attach_file))
  187.                 {
  188.                     if (i == cmax)
  189.                     {
  190.                         throw new Exception("File does not exist: " + attach_file);
  191.                     }
  192.                     System.Threading.Thread.Sleep(1000);
  193.                 }
  194.                 else
  195.                 {
  196.                     break;
  197.                 }
  198.             }
  199.  
  200.             result = new Attachment(attach_file, MediaTypeNames.Application.Octet);
  201.             // Add time stamp information for the file.
  202.             ContentDisposition disposition = result.ContentDisposition;
  203.             disposition.CreationDate = System.IO.File.GetCreationTime(attach_file);
  204.             disposition.ModificationDate = System.IO.File.GetLastWriteTime(attach_file);
  205.             disposition.ReadDate = System.IO.File.GetLastAccessTime(attach_file);
  206.             return result;
  207.  
  208.         }
  209.  
  210.         private void move_delete_file(string file1)
  211.         {
  212.             try
  213.             {
  214.                 string destfile = WorkflowScanProcessedPath + pathSep + System.IO.Path.GetFileName(file1);
  215.                 if (WorkFlowScanAfterSendAction.ToLower().Equals(delete_action))
  216.                 {
  217.                     System.IO.File.Delete(file1);
  218.                 }
  219.                 else
  220.                 {
  221.                     // Move does not have over write option
  222.                     if (System.IO.File.Exists(destfile))
  223.                     {
  224.                         System.IO.File.Delete(destfile);
  225.                     }
  226.                     System.IO.File.Move(file1, destfile);
  227.                 }
  228.             }
  229.             catch (Exception ex)
  230.             {
  231.                 throw new Exception("Source: " + file1 + " – Dest: " + WorkflowScanProcessedPath + " ",ex);
  232.             }
  233.         }
  234.         private string get_email_info(XmlDocument xd, string p)
  235.         {
  236.             string result = null;
  237.             XmlNode xn = xd.SelectSingleNode(p);
  238.             XmlNode cxn = xn.ChildNodes[0];
  239.             if (cxn is XmlCDataSection)
  240.             {
  241.                 XmlCDataSection cdataSection = cxn as XmlCDataSection;
  242.                 result = cdataSection.Value;
  243.             }
  244.             return result;
  245.         }
  246.  
  247.         public void log(String s)
  248.         {
  249.             log(s, EventLogEntryType.Information, default_EventID);
  250.         } // end method
  251.  
  252.         public void log(String s, EventLogEntryType logtype)
  253.         {
  254.             log(s, logtype, default_EventID);
  255.         } // end method
  256.  
  257.         public void log(String s, EventLogEntryType logtype, int EventID)
  258.         {
  259.             EventLog elog = getmylog();
  260.             elog.WriteEntry(this.ServiceName + " " + s, logtype, EventID);
  261.         } // end method
  262.  
  263.         private EventLog getmylog()
  264.         {
  265.             EventLog elog;
  266.             String eventsource, logName;
  267.             //This name gets registered in the windows registry in
  268.             //a place where the event log knows where to find it.
  269.             eventsource = this.ServiceName;
  270.             //Give the event log a unique name. Don’t choose
  271.             //Application, Security or System as a name
  272.             logName = "Application";
  273.             if (!EventLog.SourceExists(eventsource))
  274.             {
  275.                 EventLog.CreateEventSource(eventsource, logName);
  276.             }
  277.             elog = new EventLog();
  278.             //set the log to read and write from to the new log
  279.             elog.Log = logName;
  280.             //set the source to this program
  281.             elog.Source = eventsource;
  282.             return elog;
  283.         }
  284.     }
  285. }
  286.  

Leave a Reply

Your email address will not be published. Required fields are marked *

*


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <font color="" face="" size=""> <span style="">

Bad Behavior has blocked 523 access attempts in the last 7 days.