The use of .NET classes can extend PowerShell's reach. In this article I will be looking at the System.IO.FileSystemWatcher .NET class and how you can use it to raise notifications on files and directories.

The use of .NET classes can extend PowerShell's reach. In this article I will be looking at the System.IO.FileSystemWatcher .NET class and how you can use it to raise notifications on files and directories.

PowerShell doesn't do restrictions, and when you can't achieve something one way, there is most probably another way with PowerShell's extension ability. To use an example of notifying about file system changes, we can delve into .NET classes.

I'm going to show you how to initialize an instance of the System.IO.FileSystemWatcher class and subscribe to the events generated by a Microsoft .NET Framework object. By using a PowerShell function, I've created a way to simplify this process even further.

Before we go into the function, let's initialize an instance of the class and look at some of the properties. If you are using an older version of PowerShell, use the New-Object cmdlet; otherwise, use the static method new:

$fsw = [System.IO.FileSystemWatcher]::new()
Or:
$fsw = New-Object -TypeName System.IO.FileSystemWatcher

Viewing the initialized instance shows the properties:

FileSystemWatcher properties on a new instance

FileSystemWatcher properties on a new instance

The properties to look at are NotifyFilter, Filter, EnableRaisingEvents, Path, and IncludeSubdirectories.

The NotifyFilter property specifies changes to watch for in a file or folder. Using Get-Member on our instance shows NotifyFilter takes its values from the .NET class System.IO.NotifyFilters.

$fsw | Get-Member -Name NotifyFilter
NotifyFilter properties view by Get Member

NotifyFilter properties view by Get Member

Digging a little deeper, we see the base type on this .NET class is an Enum:

[System.IO.NotifyFilters].BaseType

NotifyFilter basetype of enum

NotifyFilter basetype of enum

By using the Enum type, we can view the values of 'System.IO.NotifyFilters':

[Enum]::GetNames('System.IO.NotifyFilters')

Enum of NotifyFilter properties

Enum of NotifyFilter properties

You can use more than one of these values when defining your NotifyFilter.

The other properties are straightforward to populate:

Path sets the directory to watch.

The Filter property takes a string, so you can define which files to watch in a directory.

IncludeSubdirectories takes a Boolean value to indicate whether to monitor subdirectories.

EnableRaisingEvents also takes a Boolean value to indicate enablement of the watcher. To be able to subscribe to the events generated by .NET Framework objects, you will need to use the Register-ObjectEvent cmdlet. For now, I will go over the function to help pull all of this together, which will include a further look into Register-ObjectEvent.

Invoke-FileSystemWatcher ^

I will start with the advanced parameters for the function. This is how they look:

function Invoke-FileSystemWatcher {
    [CmdletBinding()]
    param (
        [Parameter()]
        [System.IO.FileInfo] $Path,
        [Parameter()]
        [String] $filter = '*.*',
        [Parameter()]
        [System.IO.NotifyFilters] $NotifyFilters,
        [parameter()]
        [Switch] $Recurse,
        [parameter()]
        [Switch] $EnableRaisingEvents,
        [Parameter()]
        [EventName] $EventName,
        [Parameter()]
        [psobject] $MessageData,
        [Parameter()]
        [scriptblock] $Action
    )

The first five parameters relate to setting up the System.IO.FileSystemWatcher .NET class. The System.IO.NotifyFilters parameter can take more than one option. Defining the type allows for tab completion, which extends to more than one value. So by tabbing the first value, add a comma, then you'll be able to tab through the next. The Recurse switch will define a true or false value depending on whether you use it, which will then apply to IncludeSubdirectories.

To help add an EventName, I created an Enum. The Enum looks like this:

enum EventName {
    Created
    Changed
    Deleted
    Renamed
}

This is a handy feature added in PowerShell version 5. (You could create one before PowerShell 5 with C# code, if required.)

Sure, I could have used a ValidateSet, but it introduces another aspect of the PowerShell language. I can tab through the options like any Enum type. The MessageData parameter is part of the Register-ObjectEvent cmdlet. The description from the online help does a good job on describing its use:

Specifies any additional data to be associated with this event subscription. The value of this parameter appears in the MessageData property of all events associated with this subscription.

This can be useful, and I will provide an example at the end.

The Action parameter is optional and specifies commands to handle the event in the form of a script block. The commands in the action script block run when raising the event as opposed to sending to the event queue.

Here is the rest of the function:

$watcher = [System.IO.FileSystemWatcher]::new()
    Write-Verbose -Message "[PROCESS] Building FileSystemWatcher with parameters"
    # Build the FileSystemWatcher class
    Switch ($PSBoundParameters) {
        { $_.ContainsKey('Path') } {
            $watcher.Path = $Path.FullName
        }
        { $_.ContainsKey('Filter') } {
            $watcher.Filter = $filter
        }
        { $_.ContainsKey('NotifyFilters') } {
            $watcher.NotifyFilter = $NotifyFilters
        }
        { $_.ContainsKey('Recurse') } {
            $watcher.IncludeSubdirectories = $Recurse.ToBool()
        }
        { $_.ContainsKey('Recurse') } {
            $watcher.EnableRaisingEvents = $EnableRaisingEvents.ToBool()
        }
    }
    Write-Verbose -Message "[PROCESS] Built FileSystemWatcher, registering Event"
    # Register the Event
    $ObjectEvent = @{
        InputObject      = $watcher
        EventName        = $EventName
        SourceIdentifier = "Watching: $($Path.FullName)"
        Action           = $Action
        MessageData      = $MessageData
    }
    Register-ObjectEvent @ObjectEvent
    Write-Verbose -Message "[PROCESS] Event now registered"
}

The first part of the code is to create the instance of the .NET class, System.IO.FileSystemWatcher.

The next is to parse $PSBoundParmameters through a switch statement with each parameter value added to the properties of the instance of the FileSystemWatcher. Finally, using the splatting technique, we build the subscriber for the event and parse to Register-ObjectEvent.

I shall create an example now that will display alerts to the screen when a folder reaches a size limit. For readability, I've splatted the parameters:

$watcherEvent = @{
    Path                = 'C:\temp'
    filter              = "*.*"
    NotifyFilters       = 'LastWrite', 'DirectoryName'
    Recurse             = $true
    MessageData         = "8mb"
    EnableRaisingEvents = $true
    EventName           = 'Changed'
    Action              = {
        $Folder = (Get-ChildItem $Sender.Path -Recurse |
                Measure-Object -Property Length -Sum -ErrorAction Stop).Sum
        if ($Folder -ge $Event.MessageData) {
            $limitOver = '{0:N2}MB' -f (($Folder - $Event.MessageData) / 1mb)

            $message = "[WARNING] [{0}] {1} has exceeded the limit of {2} by {3}" -f
            $Event.TimeGenerated,
            $Sender.Path,
            $Event.MessageData,
            $limitOver
            $message | Out-Host
        }
    }
}

Before invoking this, you will notice a few variables in the Action script block parameter you might not be aware of. $Event and $Sender are automatic variables available in the event handler script block. They help provide information to us about the event. There are a few more, so it's worth reading about_automatic_variables to start with.

Let's now run the command:

Invoke-FileSystemWatcher @watcherEvent

Copying a file to C:\Temp takes me over the 8 MB limit I set in the MessageData parameter, which generates an alert:

Event subscriber alert to screen

Event subscriber alert to screen

Summary ^

Using the System.IO.FileSystemWatcher .NET class is a great way to get real-time event notifications. The ability to write code in the Action script block gives great flexibility on alert notifications. Instead of writing to the host, you could send an email, write to a log file, or perform other actions. Using the function should simplify the creation of the FileSystemWatcher to get you creating your own file and directory event notifications. The function and examples are up on my Git page.