Watch-Directory.ps1
<#
.Synopsis File change watcher and handler. Author: Roman Kuzmin .Description The script watches for changed, created, deleted, and renamed files in the given directories. On changes it invokes the specified command with change info. It is a dictionary where keys are changed file paths, values are last change types. If the command is omitted then the script outputs change info as text. The script works until it is forcedly stopped (Ctrl-C). .Parameter Path Specifies the watched directory paths. .Parameter Command Specifies the command to process changes. It may be a script block or a command name. .Parameter Filter Simple and effective file system filter. Default *.* .Parameter Include Inclusion regular expression pattern applied after Filter. .Parameter Exclude Exclusion regular expression pattern applied after Filter. .Parameter Recurse Tells to watch files in subdirectories as well. .Parameter TestSeconds Time to sleep between checks for change events. .Parameter WaitSeconds Time to wait after the last change before processing. .Link https://github.com/nightroman/PowerShelf #> param( [Parameter(Position = 1, Mandatory = 1)] [string[]]$Path, [Parameter(Position = 2)] $Command, [string]$Filter, [string]$Include, [string]$Exclude, [switch]$Recurse, [int]$TestSeconds = 5, [int]$WaitSeconds = 5 ) trap { $PSCmdlet.ThrowTerminatingError($_) } $Path = foreach ($_ in $Path) { $_ = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($_) if (!([System.IO.Directory]::Exists($_))) { throw "Missing directory: $_" } $_ } Add-Type @' using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Text.RegularExpressions; public class FileSystemWatcherHelper : IDisposable { public string[] Path; public string Filter; public bool Recurse; public DateTime LastTime { get { return _lastTime; } } public bool HasChanges { get { return _changes.Count > 0; } } OrderedDictionary _changes = new OrderedDictionary(); readonly List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>(); readonly object _lock = new object(); DateTime _lastTime; Regex _include; Regex _exclude; public void Include(string pattern) { if (!string.IsNullOrEmpty(pattern)) _include = new Regex(pattern, RegexOptions.IgnoreCase); } public void Exclude(string pattern) { if (!string.IsNullOrEmpty(pattern)) _exclude = new Regex(pattern, RegexOptions.IgnoreCase); } public void Start() { foreach (string p in Path) { FileSystemWatcher watcher = new FileSystemWatcher(p); _watchers.Add(watcher); watcher.IncludeSubdirectories = Recurse; watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; if (!string.IsNullOrEmpty(Filter)) watcher.Filter = Filter; watcher.Created += OnChanged; watcher.Changed += OnChanged; watcher.Deleted += OnChanged; watcher.Renamed += OnChanged; watcher.EnableRaisingEvents = true; } } public object GetChanges() { lock (_lock) { object r = _changes; _changes = new OrderedDictionary(); return r; } } public void Dispose() { foreach (FileSystemWatcher watcher in _watchers) watcher.Dispose(); _watchers.Clear(); _changes.Clear(); } void OnChanged(object sender, FileSystemEventArgs e) { if (_include != null && !_include.IsMatch(e.Name)) return; if (_exclude != null && _exclude.IsMatch(e.Name)) return; lock (_lock) { _changes[e.FullPath] = e.ChangeType; _lastTime = DateTime.Now; } } } '@ $watcher = New-Object FileSystemWatcherHelper $watcher.Path = $Path $watcher.Filter = $Filter $watcher.Recurse = $Recurse try { $watcher.Include($Include) } catch { throw "Parameter Include: $_" } try { $watcher.Exclude($Exclude) } catch { throw "Parameter Exclude: $_" } try { $watcher.Start() for () { Start-Sleep -Seconds $TestSeconds if (!$watcher.HasChanges) { continue } if (([datetime]::Now - $watcher.LastTime).TotalSeconds -lt $WaitSeconds) { continue } $changes = $watcher.GetChanges() if ($Command) { try { & $Command $changes } catch { "$($_.ToString())`r`n$($_.InvocationInfo.PositionMessage)" } } else { foreach ($kv in $changes.GetEnumerator()) { "$($kv.Value) $($kv.Key)" } } } } finally { $watcher.Dispose() } |