PowerShell Pipeline

Tracking Changes to a Folder Using PowerShell

Whether it is monitoring for files and folders being updated in a specific location or you want to set up a sort of Dropbox to dump files in, the options for doing any sort of monitoring against a folder (or subfolders) are very slim. You could have a scheduled task configured that could run against a folder after so many minutes and compare the contents vs. a baseline file such as a CSV or XML file. While this would certainly work, there is another way to accomplish this that provides a more real time approach as using the FileSystemWatcher class.

Using the FileSystemWatcher class, we can set up a listener against a folder and its subfolders while specifying narrow scopes of what we are looking to watch such as whether the file/folder was created or deleted as well as if the ACL on the objects have been changed. Now before you start thinking that we might have an amazing monitoring solution that can provide who did what to what, the type of data returned is minimal such as listing the name of the item as well as the previous name if it was renamed. I also mentioned that this could be used in conjunction with a Dropbox folder that could run as a consumer and handle various files dumped into the folder.

Let's take a look at the FileSystemWatcher object and examine both its properties and events that are available. The Events are the key to understanding how we can use this for monitoring using Register-ObjectEvent.

$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher | Get-Member -Type Properties,Event
[Click on image for larger view.]  Figure 1. Looking at the members of the FileSystemWatcher object.

I'm mostly concerned about the Events in this instance. We have the following Events available to use:

Name Description
Changed Occurs when a file or directory in the specified Path is changed.
Created Occurs when a file or directory in the specified Path is created.
Deleted Occurs when a file or directory in the specified Path is deleted.
Error Occurs when the instance of FileSystemWatcher is unable to continue monitoring changes or when the internal buffer overflows.
Renamed Occurs when a file or directory in the specified Path is renamed.

I'm not really concerned about the Error Event, but left it on the list for reference purposes. In the case of my dropbox idea, I am only concerned about the Created event because this will get tripped each time and item is copied or moved to the location. Now onto the properties of this object.

$FileSystemWatcher 
[Click on image for larger view.]  Figure 2. Properties of the FileSystemWatcher.

The properties that you should only be worried about are: NotifyFilter, Filter, IncludeSubdirectories and Path.

The Filter property is basically just specifying what kind of items you are looking for. It only allows for a single string to be used and you cannot use something like "*.txt|*.xls" as it is not supported.

IncludeSubdirectories gives you the recursive monitoring of subfolders underneath the Path that you specified.

NotifyFilter is where we can specify the type of properties to look at on a file or folder such as the Name or ACL of an item. The table below provides more information in regards to this.

Name Description
Attributes The attributes of the file or folder.
CreationTime The time the file or folder was created.
DirectoryName The name of the directory.
FileName The name of the file.
LastAccess The date the file or folder was last opened.
LastWrite The date the file or folder last had anything written to it.
Security The security settings of the file or folder.
Size The size of the file or folder.

Using this knowledge, I am going to track for incoming items for my Dropbox folder and state if they have been created. Nothing too crazy, just a simple example to show how this works.

$FileSystemWatcher.Path  = "C:\Users\proxb\Desktop\DropBox"
Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Created -Action {
$Object = "{0} was {1} at {2}" -f $Event.SourceEventArgs.FullPath,
$Event.SourceEventArgs.ChangeType,
$Event.TimeGenerated
$WriteHostParams = @{
ForegroundColor = 'Green'
BackgroundColor = 'Black'
Object = $Object
}
Write-Host @WriteHostParams
}
[Click on image for larger view.]  Figure 3. Event job now available to watch items.

I now have a PSJob available which won't actually start until the first event is tripped. When it has tripped, we will get a message on the console showing the name of the item as well as the type of action that took place. Since we are only monitoring for created items, the only action that will be shown are creations. I'll go ahead and drop some items into this folder and see what transpires.

[Click on image for larger view.]  Figure 4. Event is tripped on new items.

Here we see all of the items that have been added to the Dropbox folder. If we had a more useful Action scriptblock, we could have each item handled differently based on the file extension.

To make things a little easier, I wrote a function called New-FileSystemWatcher function that will help to build out multiple watchers with less effort:

Function Start-FileSystemWatcher  {
[cmdletbinding()]
Param (
[parameter()]
[string]$Path,
[parameter()]
[ValidateSet('Changed','Created','Deleted','Renamed')]
[string[]]$EventName,
[parameter()]
[string]$Filter,
[parameter()]
[System.IO.NotifyFilters]$NotifyFilter,
[parameter()]
[switch]$Recurse,
[parameter()]
[scriptblock]$Action
)
#region Build FileSystemWatcher
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
If (-NOT $PSBoundParameters.ContainsKey('Path')){
$Path = $PWD
}
$FileSystemWatcher.Path = $Path
If ($PSBoundParameters.ContainsKey('Filter')) {
$FileSystemWatcher.Filter = $Filter
}
If ($PSBoundParameters.ContainsKey('NotifyFilter')) {
$FileSystemWatcher.NotifyFilter = $NotifyFilter
}
If ($PSBoundParameters.ContainsKey('Recurse')) {
$FileSystemWatcher.IncludeSubdirectories = $True
}
If (-NOT $PSBoundParameters.ContainsKey('EventName')){
$EventName = 'Changed','Created','Deleted','Renamed'
}
If (-NOT $PSBoundParameters.ContainsKey('Action')){
$Action = {
Switch ($Event.SourceEventArgs.ChangeType) {
'Renamed' {
$Object = "{0} was {1} to {2} at {3}" -f $Event.SourceArgs[-1].OldFullPath,
$Event.SourceEventArgs.ChangeType,
$Event.SourceArgs[-1].FullPath,
$Event.TimeGenerated
}
Default {
$Object = "{0} was {1} at {2}" -f $Event.SourceEventArgs.FullPath,
$Event.SourceEventArgs.ChangeType,
$Event.TimeGenerated
}
}
$WriteHostParams = @{
ForegroundColor = 'Green'
BackgroundColor = 'Black'
Object = $Object
}
Write-Host @WriteHostParams
}
}
#endregion Build FileSystemWatcher

    #region Initiate Jobs for FileSystemWatcher
$ObjectEventParams = @{
InputObject = $FileSystemWatcher
Action = $Action
}
ForEach ($Item in $EventName) {
$ObjectEventParams.EventName = $Item
$ObjectEventParams.SourceIdentifier = "File.$($Item)"
Write-Verbose "Starting watcher for Event: $($Item)"
$Null = Register-ObjectEvent @ObjectEventParams
}
#endregion Initiate Jobs for FileSystemWatcher
}

Using my function, I will begin watching the Dropbox folder for created files and will note the extensions that could then help in handling what to do with each file.

$FileSystemWatcherParams = @{
Path = 'C:\Users\Proxb\Desktop\Dropbox'
Recurse = $True
NotifyFilter = 'FileName'
Verbose = $True
Action= {
$Item = Get-Item $Event.SourceEventArgs.FullPath
$WriteHostParams = @{
ForegroundColor = 'Green'
BackgroundColor = 'Black'
}           
Switch -regex ($Item.Extension) {
'\.(ps1|psm1|psd1)' {$WriteHostParams.Object = "Processing PowerShell file: $($Item.Name)"}
'\.(docx|doc)' {$WriteHostParams.Object = "Processing Word document: $($Item.Name)"}
'\.(xlsx|xls)' {$WriteHostParams.Object = "Processing Excel spreadsheet: $($Item.Name)"}
'\.csv' {$WriteHostParams.Object = "Processing CSV spreadsheet: $($Item.Name)"}
'\.xml' {$WriteHostParams.Object = "Processing XML document: $($Item.Name)"}
'\.exe' {$WriteHostParams.Object = "Processing Executable: $($Item.Name)"}
'\.onepkg' {$WriteHostParams.Object = "Processing OneNote package: $($Item.Name)"}
'\.lnk' {$WriteHostParams.Object = "Processing Link: $($Item.Name)"}
'\.cer|\.pfx' {$WriteHostParams.Object = "Processing Certificate File: $($Item.Name)"}
Default{$WriteHostParams.Object = "Processing File: $($Item.Name)"}
}  
$Item | Remove-Item
Write-Host @WriteHostParams
}
}

@('Created') | ForEach {
$FileSystemWatcherParams.EventName = $_
Start-FileSystemWatcher @FileSystemWatcherParams
}

[Click on image for larger view.]  Figure 5. Processing specific files in the Dropbox.

In this case, I am removing each item to simulate each file being consumed and then removed after the event action block is done with it. But you can notice that by having a well-defined Action block allows me the flexibility of handling each file sent to the drop box so it can be consumed to further automate my system.

That is all for today's article on using the FileSystemWatcher to monitor and respond to file/folder events. Drop me a comment and let me know how you are using this technique in your day to day operations!

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular