Watching for changes in SFTP/FTP server

Neither SFTP nor FTP protocol have any mechanism to notify a client about changes in a remote folder. The only solution to detect changes is to periodically enumerate remote directory tree and find differences. It’s easy to implement with use of Session.EnumerateRemoteFiles method and Compare-Object cmdlet in PowerShell or Enumerable.Except LINQ method in C#/VB.NET.

Note that, if you want to download the changes, you do not need to check for changes yourself, just run Session.SynchronizeDirectories in a loop. You can use the Keep local directory up to date extension.

Advertisement

PowerShell

param (
    # Use Generate Session URL function to obtain a value for -sessionUrl parameter.
    $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/",
    $remotePath = "/home/user"
)
 
try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "WinSCPnet.dll"
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.ParseUrl($sessionUrl)
 
    $session = New-Object WinSCP.Session
 
    try
    {
        # Connect
        $session.Open($sessionOptions)
 
        $prevFiles = $Null;
 
        while ($True)
        {
            # Collect file list
            $files =
                $session.EnumerateRemoteFiles(
                    $remotePath, "*.*", [WinSCP.EnumerationOptions]::AllDirectories) |
                Select-Object -ExpandProperty FullName
 
            if ($prevFiles -eq $Null)
            {
                # In the first round, just print number of files found
                Write-Host "Found $($files.Count) files"
            }
            else
            {
                # Then look for differences against the previous list
                $changes = Compare-Object -DifferenceObject $files -ReferenceObject $prevfiles
 
                $added =
                    $changes |
                    Where-Object -FilterScript { $_.SideIndicator -eq "=>" } |
                    Select-Object -ExpandProperty InputObject
                if ($added)
                {
                    Write-Host "Added files:"
                    Write-Host ($added -Join "`n")
                }
 
                $removed =
                    $changes |
                    Where-Object -FilterScript { $_.SideIndicator -eq "<=" } |
                    Select-Object -ExpandProperty InputObject
                if ($removed)
                {
                    Write-Host "Removed files:"
                    Write-Host ($removed -Join "`n")
                }
            }
 
            $prevFiles = $files
 
            Write-Host "Sleeping 10s..."
            Start-Sleep -Seconds 10
        }
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
}
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    exit 1
}

Advertisement

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using WinSCP;
 
class Program
{
    static int Main(string[] args)
    {
        try
        {
            // Setup session options
            SessionOptions sessionOptions = new SessionOptions
            {
                Protocol = Protocol.Sftp,
                HostName = "example.com",
                UserName = "user",
                Password = "password",
                SshHostKeyFingerprint = "ssh-rsa 2048 xxxxxxxxxxx...",
            };
 
            const string remotePath = "/home/user";
 
            using (Session session = new Session())
            {
                // Connect
                session.Open(sessionOptions);
 
                List<string> prevFiles = null;
 
                while (true)
                {
                    // Collect file list
                    List<string> files =
                        session.EnumerateRemoteFiles(
                            remotePath, "*.*", EnumerationOptions.AllDirectories)
                            .Select(fileInfo => fileInfo.FullName)
                            .ToList();
                    if (prevFiles == null)
                    {
                        // In the first round, just print number of files found
                        Console.WriteLine("Found {0} files", files.Count);
                    }
                    else
                    {
                        // Then look for differences against the previous list
                        IEnumerable<string> added = files.Except(prevFiles);
                        if (added.Any())
                        {
                            Console.WriteLine("Added files:");
                            foreach (string path in added)
                            {
                                Console.WriteLine(path);
                            }
                        }
 
                        IEnumerable<string> removed = prevFiles.Except(files);
                        if (removed.Any())
                        {
                            Console.WriteLine("Removed files:");
                            foreach (string path in removed)
                            {
                                Console.WriteLine(path);
                            }
                        }
                    }
 
                    prevFiles = files;
 
                    Console.WriteLine("Sleeping 10s...");
                    Thread.Sleep(10000);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e);
            return 1;
        }
    }
}

Advertisement

Last modified: by martin