Click here to Skip to main content
Click here to Skip to main content

Directory Monitor Service controls SSIS Package Execution

By , 2 Aug 2012
 

Introduction

Let me introduce a scenario where we need to trigger an SSIS package if we add new file to a specific directory. Here we need to have a monitor service run always and detect the change and trigger the package. So the article explains how this can be implemented.

Background

This article is an answer to the comments/questions recieved for my another article Dynamic Excel file loading with SSIS.

Using the code 

Let's define a few important aspects of the objective to accomplish

  1. Create a Windows Service and always monitor the required folder for changes. So the step is a combination of WMI events and Windows Service.
  2. Trigger a package based on the events from step 1.

Few basic tips related to Windows Service 

Installation 

  1. Open Visual Studio Command Prompt(Start>All Programs > Vistal Studio 20xx > Visual Studio Tools > Visual Studio Command Prompt.
  2. Type in the following at command prompt to install the service
  3. installutil "C:\Folder1\Folder2\Yourservice.exe"

Uninstallation 

  1. Open Visual Studio Command Prompt(Start>All Programs > Vistal Studio 20xx > Visual Studio Tools > Visual Studio Command Prompt.
  2. Type in the following at command prompt to uninstall the service.
  3. installutil /u "C:\Folder1\Folder2\Yourservice.exe"

Start/stop Windows Service 

  1. Open Command Prompt(Start>Run type in cmd  will launch Command Prompt).
  2. Next
    1. Start the Service
    2. net start Yourservicename
    3. Stop service
    4. net stop Yourservicename

Modify Settings

Any dynamic settings modification(example modifying configuration entries) would be reflected only after you do a stop and start the service. 

Formulating the WMI Query

Let's build a query to monitor any file creation(you can copy file to the location too):

string WMIQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 10 " 
                                    + " WHERE TargetInstance ISA \"CIM_DirectoryContainsFile\" and "
                                    + " TargetInstance.GroupComponent= "
                                    + " \"Win32_Directory.Name='C:\\Test\\MyFolderToMonitor'\"";  

The above WMI query will capture any file creation event occured within 10 seconds for the folder mentioned. 

Executing SSIS from C#

We can use Microsoft.SQLServer.ManagedDTS (normal location is : C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.ManagedDTS.dll ) library to execute the package from C#. 

Lets build little class to do this. Please see the following class to see how it works. It is as simple as that Load the package at the path supplied, and use Execute method of Package object. 

using Microsoft.SqlServer.Dts.Runtime;
 
namespace DirectoryMonitorService
{
    public class PackageExecuter
    {
        Package pkg;
        Application app;
        DTSExecResult pkgResults;
 
        public PackageExecuter()
        {
        }       
 
        public void Run()
        {
            app = new Application();
            pkg = app.LoadPackage("PackagePath", null);
            pkgResults = pkg.Execute();
        }
    }
}

How to make the Service Flexible 

You should be able to:

  1. Change/modify the path for "Directory to Monitor"
  2. Control the WMI query interval time
  3. Control the WMI polling thread sleep time
  4. Control the package to be executed
  5. Control the location for service and package log path

All the above things can be controlled through simple configuration file below:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
    <add key="DirectoryToMonitor" value="C:\Test\DirMon">
      <!--Set the directory path to be monitored by the service -->
    </add>
    <add key="WMIThreadSleepTimer" value="2">
      <!--Set the value of the ThreadSleep in Seconds -->
    </add>
    <add key="WMISQueryInterval" value="2">
      <!--Set the value of the WMI Query Interval in Seconds -->
    </add>
    <add key="ServiceLogSaveLocation" value="C:\Test\Logs">
      <!--Set the path to save service log entries -->
    </add>
    <add key="EnableLogging" value="true">
      <!--Set true or false to enable/disable logging -->
    </add>
    <add key="PackageToExecute" value="C:\test\KP\Package.dtsx">
      <!--Set the path for SSIS package to be executed by the service -->
    </add>
    <add key="PackageLogSaveLocation" value="C:\Test\Logs">
      <!--Set the directory path to be monitored by the service -->
    </add>
    <add key="SavePackageExecutionLog" value="true">
      <!--Set true or false to enable/disable -->
    </add>
</appSettings>
</configuration>

Managing/Handling configuration

We can build a small class to manage the configuration and keep it ready for whenever we want ie without actually going to config again.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml;
using System.Configuration;
using System.Collections.Specialized;
using System.Collections;
 
namespace DirectoryMonitorService
{
    public class ConfigReader
    {
        const string DirectoryToMonitor = "DirectoryToMonitor";
        const string WMIThreadSleepTimer = "WMIThreadSleepTimer";
        const string ServiceLogSaveLocation = "ServiceLogSaveLocation";
        const string WMISQueryInterval = "WMISQueryInterval";
        const string EnableLogging = "EnableLogging";
        const string SavePackageExecutionLog = "SavePackageExecutionLog";
        const string PackageToExecute = "PackageToExecute";
        const string PackageLogSaveLocation = "PackageLogSaveLocation";
        const string PackageProperties = "/configuration/PackageProperties";
        const string ServiceProperties = "/configuration/ServiceProperties";
       
        private string _directoryPath = "";
        public string DirectoryPath
        {
            get { return _directoryPath; }
            set { _directoryPath = value; }
        }
 
        private string _configPath = "";
        public string ConfigPath
        {
            get { return _configPath; }
            set { _configPath = value; }
        }
 
        private bool _saveLog = false;
        public bool SaveLog
        {
            get { return _saveLog; }
            set { _saveLog = value; }
        }
 
        private string _logPath = "";
        public string LogPath
        {
            get { return _logPath; }
            set { _logPath = value; }
        }
 
        private int _threadSleep=2000;
        public int ThreadSleep
        {
            get { return _threadSleep; }
            set { _threadSleep = value; }
        }
        private int _queryInterval=10;
        public int QueryInterval
        {
            get { return _queryInterval; }
            set { _queryInterval = value; }
        }
 
        private string _packagePath = "";
        public string PackagePath
        {
            get { return _packagePath; }
            set { _packagePath = value; }
        }
 
        private bool _saveResults = false;
        public bool SaveResults
        {
            get { return _saveResults; }
            set { _saveResults = value; }
        }
 
        private string _packageExecutionResultSavePath = "";
        public string PackageExecutionResultSavePath
        {
            get { return _packageExecutionResultSavePath; }
            set { _packageExecutionResultSavePath = value; }
        }
 
        private Dictionary<string, string> execArgs;
        public ConfigReader()
        {
            BuildConfiguration();
        }
        private void BuildConfiguration()
        {
            execArgs = new Dictionary<string, string>();
 
            // Get the appSettings.
            NameValueCollection appSettings = ConfigurationManager.AppSettings;
 
            IEnumerator appSettingsEnum = appSettings.Keys.GetEnumerator();
            int i = 0;
            Console.WriteLine("App settings.");
            while (appSettingsEnum.MoveNext())
            {
                string key = appSettings.Keys[i];
                string value = appSettings[key];
                execArgs.Add(key, value);
                i += 1;
            }
 
            this.DirectoryPath = execArgs[DirectoryToMonitor];
            this.SaveLog = bool.Parse(execArgs[EnableLogging]);
            this.LogPath = execArgs[ServiceLogSaveLocation];
            this.ThreadSleep = Int32.Parse(execArgs[WMIThreadSleepTimer]) * 1000;
            this.QueryInterval = Int32.Parse(execArgs[WMISQueryInterval]);
 
 
            this.PackagePath = execArgs[PackageToExecute];
            this.SaveResults = bool.Parse(execArgs[SavePackageExecutionLog]);
            this.PackageExecutionResultSavePath = execArgs[PackageLogSaveLocation];
        }
 
    }
}

The Entry Point 

Lets define this with full fledge flexibility to log events:

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Diagnostics;
using System.Configuration;
 
namespace DirectoryMonitorService
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main()
        {
 
            try
            {
                ConfigReader config = new ConfigReader();
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] 
                                { 
                                    new DirectoryMonitorService(config)
                                };
                ServiceBase.Run(ServicesToRun);
            }
            catch (Exception ex)
            {
                EventLog m_EventLog = new EventLog("");
                m_EventLog.Source = "DirectoryMonitorService";
                m_EventLog.WriteEntry(ex.ToString(), EventLogEntryType.Error);
 
            }
        }
 
    }
}

Please find full fledged working code attached. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Kuthuparakkal
Business Analyst
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionIssuesmembersvenb779 Aug '12 - 20:54 
Hello,
I have a problem with the service. I was able to build and install it and it even watches over the folder I set.
But when it triggers the package to execute, it won't process the valid file into the "processed"-folder instead it will move it to my error-folder that I set up. Then when I stop the service and start the debugging via VS I get the following message:
Error: 0xC0202009 at SSIS Load Excel, Connection-Manager 'Excel_Source': SSIS-Errorcode'DTS_E_OLEDBERROR'. OLE DB-Error.Errorcode: 0x80004005.
...
Source: 'Microsoft Office Access Database Engine' HRESULT: 0x80004005
Description: 'Unexpected error from external database driver (喭⎷￾￿͘瞜Ǣ瞜).'.
 
If I change the folder name of the solution I can then run it manually without problems but when running the service again it will mess-up the solution like above.
 
Do you have an idea what the solution for this could be?
 
Thank you!
AnswerRe: IssuesmemberKuthuparakkal9 Aug '12 - 21:21 
This seems like you are using 64 bit OS, is it so ?
Excel conn manager will not acquire conn if running in 64 bit OS. And it works from VS because you may have set the Run64BitRunTime = False.
 
So build the service for 32 bit run time, i.e Remove Any CPU and set it for 32 bit runtime specific. Then install it.
 
Thanks,
 
Kuthuparakkal
QuestionRe: Issuesmembersvenb779 Aug '12 - 23:31 
Hello,
 
you are right about the 64-bit OS and the VS setting. However in the DirectoryMonitorService project settings everything is set to x86.
I tried finding some setting for the ssis package to set it to 32-bit but no luck.
There is this Config-Manager with an active project setting which is set to default but I can't create a new one.
 
If you got more ideas I'd be happy to try them out.
 
thank you.
AnswerRe: IssuesmemberKuthuparakkal10 Aug '12 - 0:22 
Please try:
Target Frame Work Setting
 
Also if that doesnt work try this :
Core Flags
QuestionRe: Issues [modified]membersvenb7710 Aug '12 - 1:14 
Hello again,
 
it took me a while to realize that the files the service "touches" and moves to the error folder somehow get "destroyed" and produce the error description I posted earlier.
So I can't process those files even without the service. So I deleted them and copied the "same" files to the folder and then I could run the ssis package at least.
But no luck with the service yet. I'll look into the links you posted. Very mystical OMG | :OMG:
 
Could it be that the 64-bit dtexec gets called? I have both versions on the system.
 
Maybe it is important: I'm testing this on a Windows Server 2008 R2 machine.

modified 10 Aug '12 - 7:37.

AnswerRe: IssuesmemberKuthuparakkal10 Aug '12 - 2:13 
If the service executed with x64 run-time then the created process will be 64 bit process and thus, 64 bit Dtexec called.
 
On the otherside if 32 bit process try to load 64 bit DLLs then it will definitely fail. In your case, please verify logs, and see any error message you get.
 
In my view(as per the file movement to error folder on failure) I strongly feel the package is getting executed but with 64 bit run time.
 
Try to create a simple package, that just do some basic tasks like sql (delete few records) and verify it runs.
 
Also you can verify you are running 32 bit process...
 
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName will fail, if the application is compiled for 32 bit runtime. Becoz by default 64 bit OS does not allow 32 bit process hijack. So when you call System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName would result in failure : When your process(win service) is a 32 bit process on 64 bit OS.
 

Note On Missing Error Folder File : Verify the the error folder exists, or service has permission to copy to this folder. Also if you have used any relative path, the file might have moved to non existent directory or some locations relative.
 
Thanks,
 
Kuthuparakkal
QuestionRe: Issues [modified]membersvenb7712 Aug '12 - 22:03 
Hello,
 
I have used a different package now for testing which ran without any complications. I only changed the package path and watch-folder. So I guess it must be the package that creates the error?
Could I modify the code to have it execute the 32-bit dtexec shell command?
 
I will now download your package and try with the original version. Can you verify that your version works?
 
UPDATE: I couldn't get the original package to compile.
Instead of using the dts package can't I just run the dtexec shell command from within the service? If yes, I would need some help how to start the dtexec.
 
Error Log:
OnError,SGDBSERV1,NT-AUTORITÄT\SYSTEM,Load Excel...,SSIS-Errorcode 'DTS_E_CANNOTACQUIRECONNECTIONFROMCONNECTIONMANAGER'. Errorcode: 0xC0202009
 

thank you.

modified 13 Aug '12 - 8:52.

GeneralRe: IssuesmemberKuthuparakkal18 Aug '12 - 18:44 
My version works!
 
Thank you
QuestionFile Change NotificationmemberDewey2 Aug '12 - 15:01 
You said it will capture a creation event, but does that include a change event as well?
 
By change I mean, if the file already exists in the directory, but someone opens it and changes something, then saves it in that directory, will this detect that?
 
Thanks
AnswerRe: File Change Notificationmemberkuthuparakkal2 Aug '12 - 15:30 
Yes.. It responds to changes other than deletion. save, rename, create will get captured
GeneralRe: File Change NotificationmemberDewey14 Aug '12 - 16:28 
Thank you!
GeneralRe: File Change NotificationmemberKuthuparakkal18 Aug '12 - 18:43 
You are most welcome! Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 2 Aug 2012
Article Copyright 2012 by Kuthuparakkal
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid