Monitoring Folder for files and moving to another location using Windows Services


In this article we are going to see how we can monitor multiple folders for specific type of files and move them to destination folder. We are using Windows service to implement this functionality using Multithreading concept.

In this article we are going to see how we can monitor multiple folders for specific type of files and move them to destination folder. We are using Windows service to implement this functionality using Multithreading concept.

Step 1:
Create a Windows Service using Microsoft Visual Studio. Please refer to my previous article Windows Service Part1

Step 2:
In the Debug folder of the Windows Service, Create an xml file with the name Configuration.xml. In this file we will specify which folders are being monitored using our windows service. We will specify

1.IncomingPath:Directory to monitor for the files.
2.OutgoingPath:After the file is processed by the thread it is moved to the path specified using OutgoingPath.
3.FileType:The type of the file to monitor in the directory mentioned in the IncomingPath.

Configuration file looks as shown below:


<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<FileWorkerOptions>
<FileWorkerOption>
<IncomingPath>C:\Temp\Incoming1</IncomingPath>
<OutgoingPath>C:\Temp\Outgoing</OutgoingPath>
<FileType>*.txt</FileType>
</FileWorkerOption>
<FileWorkerOption>
<IncomingPath>C:\Temp\Incoming2</IncomingPath>
<OutgoingPath>C:\Temp\Outgoing</OutgoingPath>
<FileType>*.dat</FileType>
</FileWorkerOption>
<FileWorkerOption>
<IncomingPath>C:\Temp\Incoming3</IncomingPath>
<OutgoingPath>C:\Temp\Outgoing</OutgoingPath>
<FileType>*.inf</FileType>
</FileWorkerOption>
</FileWorkerOptions>
</Configuration>



Step 3:
Right Click the project -> Go to Properties -> click on Settings tab -> Enter the following details
Name:Configuration
Type:string
Scope:Application
Value:Configuration.xml

Save and close this window. These settings represents the application settings which can be used across the project.

Step 4:
Add ThreadActionState class to the service which indicates whether the servie is in paused state or stopped.

public static class ThreadActionState
{

//indicates whether the service is paused, telling the processing threads to pause
private static bool m_Pause = false;

public static bool Pause
{
get { return m_Pause; }
set { m_Pause = value; }
}
//To indicate that the service is shutting down and threads need to exit.
private static bool m_Stop = false;

public static bool StopThread
{
get { return m_Stop; }
set { m_Stop = value; }
}
}


Step 5:
Now add a FileWorkerOptions class. We will create an instance of this class for each folder which we will monitor for a specific file type.

public class FileWorkerOptions
{
private string m_Output;

public string Output
{
get { return m_Output; }
set { m_Output = value; }
}
private string m_Input;

public string Input
{
get { return m_Input; }
set { m_Input = value; }
}
private string m_FileType;

public string FileType
{
get { return m_FileType; }
set { m_FileType = value; }
}
}


Step 6:
Add a File Worker class, In this class we are going to write the logic to move files from source directory to the destination directory.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Xml;
using System.Collections;

namespace MyDemoService
{
public class FileWorker
{
}
}


Step 7:
In this class Add a constructor which takes a parameter of type "FileWorkerOptions", as seen above file class instance is used to specify the source,target and the file type. in the constructor we will assign it to a local "FileWorkerOptions" instance.

public FileWorker(FileWorkerOptions fwo)
{
m_FileWorkerOptions = new FileWorkerOptions();
m_FileWorkerOptions.Input = fwo.Input;
m_FileWorkerOptions.Output = fwo.Output;
m_FileWorkerOptions.FileType = fwo.FileType;
}

Step 8:
write the below code in the FileWorker class, it is used to control thread.

private Thread m_WorkerThread = null;
private FileWorkerOptions m_FileWorkerOptions;
private const int THREAD_WAIT = 5000;

public Thread WorkerThread
{
get { return m_WorkerThread; }
}

Step 9:
Write the below method which is used to log an event in the EventLog.

public static void WriteLogEvent(string message, long id, EventLogEntryType eType, string source)
{
try
{
EventLog eLog = new System.Diagnostics.EventLog("Application");
eLog.Source = source;
EventInstance eInstance = new EventInstance(id, 0, eType);
eLog.WriteEvent(eInstance, message);
eLog.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}


Step 10:
Write a ThreadMethod function which is called by the thread. In this method we will write code to move files from Source to Target directory.

private void ThreadMethod()
{
{
{
try
{
foreach (string textFile in Directory.GetFiles(m_FileWorkerOptions.Input, m_FileWorkerOptions.FileType, SearchOption.TopDirectoryOnly))
{
if (ThreadActionState.Pause || ThreadActionState.StopThread)
{
break;
}
try
{
string fileName = textFile.Substring(textFile.LastIndexOf('\\'));
File.Move(textFile, m_FileWorkerOptions.Output + fileName);
WriteLogEvent("Thread Message Info:" + textFile, MyConfig.THREAD_INFO, EventLogEntryType.Information, "DemoService");
}
catch (Exception ex)
{
WriteLogEvent("Thread Method Information - " + DateTime.Now.ToString(), MyConfig.THREAD_ERROR, EventLogEntryType.Information, "DemoService");
}
}
}
catch (IOException fio)
{

WriteLogEvent("Thread Method Abort err - " + DateTime.Now.ToString(), MyConfig.IO_ERROR, EventLogEntryType.Error, "DemoService");
}
catch (ThreadAbortException tabEx)
{
//Clean up the thread here
WriteLogEvent("Thread IO error - " + DateTime.Now.ToString(), MyConfig.THREAD_ABORT_ERROR, EventLogEntryType.Error, "DemoService");
}
catch (Exception ex)
{
WriteLogEvent("Thread Method Error - " + DateTime.Now.ToString(), MyConfig.THREAD_ERROR, EventLogEntryType.Error, "DemoService");
}
}

//check before going to sleep whether you were supposed to stop.
if (!ThreadActionState.StopThread)
Thread.Sleep(THREAD_WAIT);
}
}

In the above code, We are looking for a file of a specific type as specified in the FileWorkerOptions class instance in the Source Folder and moving it to Target folder. Here we are also checking if the thread is not paused or stopped. In case of any exceptions they are logged into EventLog.
Step 11:
Finally we write the code to Start the thread for each source folder.

public void Start()
{
m_WorkerThread = new Thread(ThreadMethod);
m_WorkerThread.Priority = ThreadPriority.Normal;
m_WorkerThread.IsBackground = true;
m_WorkerThread.Start();
}

Step 12:
In the Service class file write the below code. In the below code we are handling the Start,Stop,Pause,Continue events of the service. In the OnStart event we will initiate the thread which calls the ThreadMethod function of the service class. In the ThreadMethod class, we will continously monitor the folder till the thread is not stopped. Here we will read the entries from the "Configuration.xml" file and read the Source Folder,Target Folder and the File Type and assign it to FileWorkerOptions class instance. Then we are creating FileWorker class instance and adding it to a list of worker threads. Now call the Start method of FileWorker class which will process the files in the Source directory for a specific type and moves them to destination folder.


namespace MyDemoService
{

public partial class DemoService : ServiceBase
{
//To request additional shutdown time from the SCM to complete processing and cleanup before we exit.
private const int THIRTY_SECONDS = 30000;
//to wait for 15 seconds for the thread to complete its task or shut down itself.
private const long TIME_OUT = 15000;
List m_WorkerThreads = new List();


private Thread m_WorkerThread = null;//available to any method in the service, except directly by the threads
private const int THREAD_WAIT = 5000;
private FileWorkerOptions m_FileWorkerOptions;

public DemoService()
{
InitializeComponent();
}

protected override void OnStart(string[] args)
{
try
{
Thread.Sleep(15000);
m_WorkerThread = new Thread(ThreadMethod);
m_WorkerThread.Name = "Demo Service Worker Thread";
m_WorkerThread.Priority = ThreadPriority.Normal;
m_WorkerThread.Start();
WriteLogEvent("Demo Service Starting", MyConfig.ONSTART_ERROR, EventLogEntryType.Information, "DemoService");
}
catch (Exception ex)
{
//Catch the exception to avoid unhandled errors and stop the service.
this.Stop();
}
}

protected override void OnStop()
{
try
{
WriteLogEvent("Demo Service Stopping", MyConfig.ONSTOP_ERROR, EventLogEntryType.Information, "DemoService");
ThreadActionState.StopThread = true;

foreach (FileWorker fw in m_WorkerThreads)
{
this.RequestAdditionalTime(THIRTY_SECONDS);
m_WorkerThread.Join(15000);
}
}
catch (Exception ex)
{
m_WorkerThread = null;
WriteLogEvent(ex.ToString(), MyConfig.ONSTOP_ERROR, EventLogEntryType.Error, "DemoService");

}
}

protected override void OnPause()
{
try
{
ThreadActionState.Pause = true;
WriteLogEvent("Demo Service Pausing", MyConfig.ONPAUSE_INFO, EventLogEntryType.Information, "DemoService");
}

catch (Exception ex)
{
//Catch the exception to avoid unhandled errors and write the output to the debug window.
Debug.WriteLine("Error Pausing service: " + ex.ToString());
this.Stop();
}
}

protected override void OnContinue()
{
try
{
//to indicate that threads can continue their work.
ThreadActionState.Pause = false;
WriteLogEvent("Demo Service Continuing", MyConfig.ONCONTINUE_INFO, EventLogEntryType.Information, "DemoService");

}
catch (Exception ex)
{
//Catch the exception to avoid unhandled errors and write the output to the debug window.
Debug.WriteLine("Error resuming service: " + ex.ToString());
this.Stop();
}
}

//RUN The code,sleep for 5 secs and then run the code again
private void ThreadMethod()
{
var m_WorkerThreads = new List();
{
{
try
{
while (!ThreadActionState.StopThread)
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
XmlDocument doc = new XmlDocument();
//Load the configuration file.
doc.Load(Properties.Settings.Default.Configuration);
//Reference the Outer Node
XmlNode rootNode = doc.SelectSingleNode("//*[local-name()='FileWorkerOptions']");
if (rootNode != null)
{
//Reference the first child node of FileWorkerOptions
System.Xml.XPath.XPathNavigator tmpNode = rootNode.FirstChild.CreateNavigator();
if (tmpNode != null)
{
FileWorkerOptions fwOptions = new FileWorkerOptions();
System.Xml.XPath.XPathNavigator children;
//iterate through each child node(FileWorkerOption) & get the values. Create a new FileWorkerOption instance and FileWorkerOptions instance.
do
{
try
{
children = tmpNode.SelectSingleNode("IncomingPath");
fwOptions.Input = children.Value;
children = tmpNode.SelectSingleNode("OutgoingPath");
fwOptions.Output = children.Value;
children = tmpNode.SelectSingleNode("FileType");
fwOptions.FileType = children.Value;

FileWorker tmpFileWorker = new FileWorker(fwOptions);
m_WorkerThreads.Add(tmpFileWorker);
tmpFileWorker.Start();

}
catch (Exception ex)
{
WriteLogEvent("Could not read key/value pair", MyConfig.ONSTART_ERROR, EventLogEntryType.Error, "DemoService");

}
}
while (tmpNode.MoveToNext());
}
}

}
}
catch (IOException fio)
{

WriteLogEvent("Thread Method Abort err - " + DateTime.Now.ToString(), MyConfig.IO_ERROR, EventLogEntryType.Error, "DemoService");
}
catch (ThreadAbortException tabEx)
{
//Clean up the thread here
WriteLogEvent("Thread IO error - " + DateTime.Now.ToString(), MyConfig.THREAD_ABORT_ERROR, EventLogEntryType.Error, "DemoService");
}
catch (Exception ex)
{
WriteLogEvent("Thread Method Error - " + DateTime.Now.ToString(), MyConfig.THREAD_ERROR, EventLogEntryType.Error, "DemoService");
}
}

//check before going to sleep whether you were supposed to stop.
if (!ThreadActionState.StopThread)
Thread.Sleep(THREAD_WAIT);
}
}

private static void WriteLogEvent(string message, long id, EventLogEntryType eType, string source)
{
try
{
EventLog eLog = new System.Diagnostics.EventLog("Application");
eLog.Source = source;
EventInstance eInstance = new EventInstance(id, 0, eType);
eLog.WriteEvent(eInstance, message);
eLog.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
}


public class MyConfig
{
public const int ONSTART_ERROR = 1000;
public const int ONSTOP_ERROR = 1001;
public const int THREAD_ERROR = 1002;
public const int THREAD_ABORT_ERROR = 1003;
public const int ONCONTINUE_ERROR = 1004;
public const int ONSTART_INFO = 2000;
public const int ONSTOP_INFO = 2001;
public const int THREAD_INFO = 2002;
public const int ONPAUSE_INFO = 2003;
public const int ONCONTINUE_INFO = 2004;
public const int IO_ERROR = 3000;
}
}


MyConfig class is used to represent different kinds errors/information using eventinstanceId.

Build the code and Deploy it using installutil utility. Create the below folders
C:\Temp\Incoming1
C:\Temp\Incoming2
C:\Temp\Incoming3
C:\Temp\Outgoing

Add text documents to Incoming1 folder.
Add .dat files to Incoming2 folder.
Add .info files to Incoming3 folder.

Type Services.msc in the Start ->run windows. This will open the services windows which will list all the services running on your computer. Start the "MyWindowsService" which we installed. Once the service is started, Notice that all the files are moved to Outgoing folder.

Some important points to note
1.We have used Threads because Threads allows the service to perform multiple actions in a concurrent manner.
2.We are logging errors in the EventLog. You can view the status of the service in the EventViewer. Start ->Run ->EventVwr -> Expand Windows Logs in the left pane -> Select Application -> next to it you will find all the event logged for our service.
3.This service must run under LocalService account.
4.This service monitors all the files added to a specified folder.


Comments

No responses found. Be the first to comment...


  • Do not include your name, "with regards" etc in the comment. Write detailed comment, relevant to the topic.
  • No HTML formatting and links to other web sites are allowed.
  • This is a strictly moderated site. Absolutely no spam allowed.
  • Name:
    Email: