A Reusable File System Event Watcher for PowerShell

Avatar

Wolfgang

Some time ago I wanted to sync files from a source directory to a destination directory immediately after they had changed in the source directory. As a C# developer I’m aware of a .NET Framework class named FileSystemWatcher which suits this job perfectly. A file system watcher listens to change notifications generated by the operating system and invokes a given function if the file change matches several filter criteria like the directory, the file name or the type of the change. There are already many examples on the internet showing how to create and configure the watcher in PowerShell but this isn’t something I can easily recall from memory at the moment I need it. I made the FSWatcherEngineEvent PowerShell module to make these file system watchers easier to use. It hides the C#-API behind a PowerShell command with argument completion, it keeps track of the created watchers, and provides commands to pause notifications and to clean up the watchers if they are no longer needed.

After you install and import the module, you can create a new filesystem watcher. As an example, you can watch for changes in directory C:\Tempfiles. The command allows to specify the same parameters (with the same names) as if you are using the C# class directly. This includes:

  • NotifyFilter: what kind of change triggers an event (by default: LastWrite, FileName, DirectoryName)
  • Filter: a wildcard to define a subset of files to watch
  • IncludeSubdirectory: extends the area of observation to the subdirectories of the specified path

Please refer to the Microsoft’s reference documentation of the FileSystemWatcher class for the details.

New-FileSystemWatcher -SourceIdentifier "MyEvent" -Path C:\Tempfiles

The watcher now sends notifications to PowerShell’s engine event queue using the source identifier “MyEvent”. You can consume the event by registering an event handler for the same source identifier. The following example just writes the whole event converted to JSON to the console:

PS> Register-EngineEvent -SourceIdentifier "MyEvent" -Action { $event | ConvertTo-Json | Write-Host }

Id     Name      PSJobTypeName   State         HasMoreData     Location    Command
--     ----      -------------   -----         -----------     --------    -------
1      MyEvent                   NotStarted    False                       |ConvertTo-Json|Wr…

PowerShell allows you to register more than one handler for a single source identifier but the FSWatcherEngineEvent module doesn’t allow you to create more than one watcher using the same source identifier.

To produce a new event, just write some characters to a file in the watched directory:

PS> "XYZ" >> C:\Tempfilesxyz

{
  "ComputerName": null,
  "RunspaceId": "b92c271b-c147-4bd6-97e4-ffc2308a1fcc",
  "EventIdentifier": 4,
  "Sender": {
    "NotifyFilter": 19,
    "Filters": [],
    "EnableRaisingEvents": true,
    "Filter": "*",
    "IncludeSubdirectories": false,
    "InternalBufferSize": 8192,
    "Path": "D:\\tmp\files\\",
    "Site": null,
    "SynchronizingObject": null,
    "Container": null
  },
  "SourceEventArgs": null,
  "SourceArgs": null,
  "SourceIdentifier": "MyEvent",
  "TimeGenerated": "2021-03-13T21:39:50.4483088+01:00",
  "MessageData": {
    "ChangeType": 4,
    "FullPath": "C:\\temp\\files\\xyz",
    "Name": "xyz"
  }
}

Events raised before a handler is registered remain in the queue until you consume them using Get-Event/Remove-Event.

To suspend the notification temporarily and to resume it later the following two commands can be used:

Suspend-FileSystemWatcher -SourceIdentifier "MyEvent"

Resume-FileSystemWatcher -SourceIdentifier "MyEvent"

To keep track of all the filesystem watchers created in the current PowerShell process, you can use the command Get-FileSystemWatcher:

PS>  Get-FileSystemWatcher

SourceIdentifier      : MyEvent
Path                  : C:\Tempfiles
NotifyFilter          : FileName, DirectoryName, LastWrite
EnableRaisingEvents   : True
IncludeSubdirectories : False
Filter                : *

This command writes a state object to the pipe containing the configuration of all filesystem watchers. Finally, if you want get rid of filesystem watchers the command Remove-FileSystemWatcher disposes a filesystem watcher specified by the source identifier or by piping in a collection of watchers:

# to dispose one watcher
Remove-FileSystemWatcher -SourceIdentifier "MyEvent"

# to dispose all 
Get-FileSystemWatcher | Remove-FileSystemWatcher

Piping the filesystem watchers also works with the other commands. If you like the module and want to see more you may inspect or fork its code at GitHub.

2 comments

Leave a comment

  • Avatar
    Fleet Command

    Something is wrong with the syntax highlighter, more than usual. The syntax highlighting in this blog isn’t something to brag about but at line 14, we have:

    "Path": "D:\tmp\files\",

    In this case, I think \” is throwing the syntax highlighter into chaos. Maybe if the JSON snippet was put into its own code fence and marked as JSON, the problem would go away.

    • Avatar
      Sean WheelerMicrosoft employee

      Thanks for pointing that out. There is an issue with the code that copies the markdown from GitHub to WordPress. It mangles the backslashes in code blocks. We have to fix them manually. This is fixed now.