Friday, 14 November 2014

Create Windows Service to watch files for SSIS

Case
I want to use the WMI Event Watcher Task to watch for new files in SSIS, but it doesn't allow me to watch subfolders and after catching an event it continues with the next task, but meanwhile it doesn't watch for new files until in starts again. Is there an alternative?
WMI Event Watcher Task






















Solution
An alternative could be to create a Windows Service that does the watching part and then executes a package when a new file arrives. For this example I used the full version of Visual Studio 2010 (SSDT BI is not enough). The example is in C#, but you can easily convert that to VB.NET.

1) Windows Service Project
Start Visual Studio and create a new Windows Service Project. I used the 4.0 framework since I want to execute SSIS 2012 packages. For 2008 you can use 3.5.
Windows Service 4.0 C#




















2) App.Config
To avoid hardcoded connection strings, paths and filters, we first have to add an Application Configuration File (App.config). Right Click the project in the solution explorer, choose Add, New Item... and then Application Configuration File. After adding the app.config we need to edit it. You can copy the XML below to start with and add extra parameters at the end. Make sure the values are correct.
app.config


















<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <appSettings>
  <add key="WatcherPath" value="d:\MySourceFiles"/>
  <add key="WatcherFilter" value="*.csv"/>
  
  <add key="SqlConnectionString" value="Data Source=.\SQL2012;Initial Catalog=master;Integrated Security=SSPI;"/>
  <add key="Catalog" value="SSISDB"/>
  <add key="Folder" value="MyFolder"/>
  <add key="Project" value="MyProject"/>
  <add key="Package" value="MyPackage.dtsx"/>

  <add key="LogFile" value="d:\FileWatcherForSSIS\log.txt"/>
 </appSettings>
</configuration>
MyCatalogStructure












3) Add References
Right click references in the Solution Explorer and choose Add Reference... For the configuration part we need to add two extra references that can be found in the .NET tab:
- System.Configuration.dll
- System.Configuration.Install.dll
Add References























For executing the SSIS packages we need four extra references that can be found in the GAC (MSIL) folder. The exact place could vary, but here are mine for SSIS 2012. Use the browse tab in the Add Reference Window to add them.
C:\Windows\assembly\GAC_MSIL\Microsoft.SqlServer.ConnectionInfo\11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.ConnectionInfo.dll
C:\Windows\assembly\GAC_MSIL\Microsoft.SqlServer.Management.Sdk.Sfc\11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.Management.Sdk.Sfc.dll
C:\Windows\assembly\GAC_MSIL\Microsoft.SqlServer.Smo\11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.Smo.dll
C:\Windows\assembly\GAC_MSIL\Microsoft.SqlServer.Management.IntegrationServices\11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.Management.IntegrationServices.dll


4) Services1.cs
Now open the source code of Services1.cs to add the actual Windows Services code. In the grey designer surface press F7 or click on the link to switch to code view. Copy the code below. If you don't want to use the Project Deployment Model then you must slightly change the code in the watcher_Created method.
// C# code
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // Added
using System.ComponentModel;
using System.Configuration;           // Added
using System.Data;
using System.Data.SqlClient;          // Added
using System.Diagnostics;
using System.IO;                      // Added
using System.Linq;
using System.ServiceProcess;
using System.Text;
using Microsoft.SqlServer.Management.IntegrationServices;  // Added

namespace FileWatcherForSSIS
{
  public partial class Service1 : ServiceBase
  {
    public Service1()
    {
      InitializeComponent();
    }

    /// <summary>
    /// Method that executes when the services starts
    /// </summary>
    /// <param name="args"></param>
    protected override void OnStart(string[] args)
    {
      // Create FileSystemWatcher dynamically instead of via the designer
      FileSystemWatcher watcher = new FileSystemWatcher();

      // Determine the folder you are watching
      watcher.Path = ConfigurationManager.AppSettings["WatcherPath"];

      // Determine for which files to watch
      watcher.Filter = ConfigurationManager.AppSettings["WatcherFilter"];

      // Determine whether you also want to watch subfolders
      watcher.IncludeSubdirectories = true;

      // Determine for which changes to watch (multiple notify
      // filters should be seperated by a pipeline |
      watcher.EnableRaisingEvents = true;
      //watcher.NotifyFilter = NotifyFilters.CreationTime;

      // Determine what to do for which events
      watcher.Created += new FileSystemEventHandler(watcher_Created);

      // Log start of service
      logText("FileWatcher started watching " + watcher.Path);
    }

    /// <summary>
    /// Method that executes when the services stops
    /// </summary>
    protected override void OnStop()
    {
      // Log start of service
      logText("FileWatcher stopped watching ");
    }

    // Define the event handlers. 
    private static void watcher_Created(object sender, FileSystemEventArgs e)
    {
      // Log start of service
      logText("FileWatcher catched " + e.FullPath);

      // Connection to the database server where the packages are located
      using (SqlConnection ssisConnection = new SqlConnection(ConfigurationManager.AppSettings["SqlConnectionString"]))
      {
        try
        {
          // SSIS server object with sql connection
          IntegrationServices ssisServer = new IntegrationServices(ssisConnection);

          // Get values from app.config
          string catalog = ConfigurationManager.AppSettings["Catalog"];
          string folder = ConfigurationManager.AppSettings["Folder"];
          string project = ConfigurationManager.AppSettings["Project"];
          string package = ConfigurationManager.AppSettings["Package"];

          // The reference to the package which you want to execute
          PackageInfo ssisPackage = ssisServer.Catalogs[catalog].Folders[folder].Projects[project].Packages[package];

          // Create collection of parameters
          Collection<PackageInfo.ExecutionValueParameterSet> executionParameter = new Collection<PackageInfo.ExecutionValueParameterSet>();

          // Add a package parameter
          executionParameter.Add(new PackageInfo.ExecutionValueParameterSet { ObjectType = 30, ParameterName = "MyFilePathParameter", ParameterValue = e.FullPath });

          // Add a logging level parameter: 1=basic, 2=performance
          executionParameter.Add(new PackageInfo.ExecutionValueParameterSet { ObjectType = 50, ParameterName = "LOGGING_LEVEL", ParameterValue = 1 });

          // Execute package asynchronized and get
          // the identifier of the execution
          long executionIdentifier = ssisPackage.Execute(false, null, executionParameter);

          // Prevent 30 second time-out by getting the execution
          // by its ID and then wait until it's completed
          ExecutionOperation executionOperation = ssisServer.Catalogs[catalog].Executions[executionIdentifier];

          // Wait while package is busy
          while (!(executionOperation.Completed))
          {
            // Refresh and wait 5 seconds before retesting
            executionOperation.Refresh();
            System.Threading.Thread.Sleep(5000);
          }

          // Log package completion
          logText("Package completed " + package);
        }
        catch (Exception ex)
        {
          logText("FileWatcher error " + ex.Message);
        }
      }
    }

    /// <summary>
    /// Write message to log
    /// </summary>
    /// <param name="logText">the log message</param>
    private static void logText(string logText)
    {
      // Simple log version. You can replace it by for example an eventlog writer
      using (FileStream fs = new FileStream(ConfigurationManager.AppSettings["LogFile"], FileMode.Append, FileAccess.Write))
      using (StreamWriter sw = new StreamWriter(fs))
      {
        sw.WriteLine(DateTime.Now.ToString() + " - " + logText);
      }
    }
  }
}


5) Installer.cs
To make the Windows Services installable we need to add an Installer Class by right clicking the Solution Explorer and choose Add, New Item..
Installer.cs


















6) Add Service(Process)Installer
Next you can drag ServiceInstaller and ServiceProcessInstaller from the toolbox to the designer surface of install.cs. If they are not available in the toolbox then you need to add them manually by right clicking the toolbox and choose Choose Items... (just like in SSIS 2008 when adding Third Party tasks).
Service(Process)Installer


















7) Edit Service(Process)Installer
In the properties of serviceInstaller1 you can set values for properties like ServiceName, DisplayName and the Description of the service.
Service Properties


























8) Building & Deploying
Now build the project in release mode (via build menu or Ctrl + Shft + B) and go to the bin release folder of your project. Copy these files to a suitable location for the service.
Bin Release Folder
















9) InstallUtil
Now open the Visual Studio 201X command prompt as Administrator (otherwise you don't have rights to add a new service). In the command prompt go to the folder where the service is located.
Then enter the following command: installutil FileWatcherForSSIS.exe. During installation it will ask for the account that runs the service. The user must be able to read the folder and to execute the package (just like the user in a SQL Server Agent jobstep). For your production server without Visual Studio (and without installutil.exe) you need to create a setup project in Visual Studio to install your new Windows Services.
Visual Studio Command Prompt (run as Administrator)















10) Start Service and test
Now go to your local services and start the new Windows Service. Then start watch the log while adding a new file in the folder that you are watching.
The new Windows Service

















Log file











Download Visual Studio 2010 Example Project

Note: This Windows Service must be installed on the same machine as SSIS.