System.IO.FileSystemWatcher only notifies once on edited files - powershell

The script uses file system watcher to monitor a honeypot folder, and report back any changes (edit, rename, delete or create), then performs some actions.
The actions work fine when creating, renaming and deleting.
But when editing, I can only make the script trigger the actions once. So for example, if a test device tries to edit a file on honeypot folder, the actions are triggered. But is the same device tries to edit again the same file or a different file, the watcher for editing seems to not work because the actions are not triggered.
So I tried to reset the script every 5 minutes via task scheduler (start the script every 5 minutes), but still same results.
Here's the code:
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\vmserver2\_Do_Not_Delete_Or_Rename"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
#Add-content "D:\log.txt" -value $logline
#write-host $logline
$targetdevice = Get-SmbOpenFile |
select clientusername, clientcomputername, path |
where {$_.Path -like 'E:\Data\Archive\_Do_Not_Delete_Or_Rename' }
$targetIP = $targetdevice.clientcomputername
$targetUser = $targetdevice.clientusername
Send-ToEmail -email "edu.bit.es#gmail.com" $targetIP
$targetUser
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
Register-ObjectEvent $watcher "Changed" -Action $action
Register-ObjectEvent $watcher "Deleted" -Action $action
Register-ObjectEvent $watcher "Renamed" -Action $action
while ($true) {sleep 5}
I'm pretty new to powershell so I don't understand while the watchers for the rest of events work and only editing does not work.

Your core logic is sound.
If you simplify your action block to only do the Write-host part, it should always work.
I believe your problem is that on the second call, you get a terminating error that you didn't catch and in return, it stop the events from triggering.
Try replacing your action scriptblock with the following
$action = {
try {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
#Add-content "D:\log.txt" -value $logline
#write-host $logline
$targetdevice = Get-SmbOpenFile |
select clientusername, clientcomputername, path |
where {$_.Path -like 'E:\Data\Archive\_Do_Not_Delete_Or_Rename' }
$targetIP = $targetdevice.clientcomputername
$targetUser = $targetdevice.clientusername
Send-ToEmail -email "edu.bit.es#gmail.com" $targetIP
$targetUser
}
catch {
Write-Host 'an error was thrown :(... Fortunately, it was caught.'
}
}
This should correct your issue of the changed event triggering only once.
Here's an example using 3 watchers that check for a file change over the same directory.
You will notice that after the counter reached 5, only 2 of the 3 watchers continue to work properly. The one that do not produce any errors watcherNoError and the one that produce a terminating error but was caught in a Try catch watcherErrorsTryCatch.
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$PathToWatch = "\\127.0.0.1\c$\_"
$watcherNoError = New-Object System.IO.FileSystemWatcher -Property #{Path = $PathToWatch;Filter = '*.*';IncludeSubdirectories=$true;EnableRaisingEvents = $true}
$watcherWithErrors = New-Object System.IO.FileSystemWatcher -Property #{Path = $PathToWatch;Filter = '*.*';IncludeSubdirectories=$true;EnableRaisingEvents = $true}
$watcherErrorsTryCatch = New-Object System.IO.FileSystemWatcher -Property #{Path = $PathToWatch;Filter = '*.*';IncludeSubdirectories=$true;EnableRaisingEvents = $true}
$Global:Counter = #{
watcherNoError = 0
watcherWithErrors = 0
watcherErrorsTryCatch = 0
}
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
Switch ($Event.MessageData.Name) {
"NoErrors" {
$Global:Counter.watcherNoError +=1
$count = $Global:Counter.watcherNoError
}
"WithErrors" {
$Global:Counter.watcherWithErrors +=1
$count = $Global:Counter.watcherWithErrors
if ($count -eq 5) {
Write-Host 'A terminated errow will be thrown...' -ForegroundColor DarkMagenta
Throw 'oh no !'
}
}
"WithErrorsTryCatch" {
$Global:Counter.watcherErrorsTryCatch +=1
$count = $Global:Counter.watcherErrorsTryCatch
if ($count -eq 5) {
try {
Throw 'oh no !'
}
catch {
Write-Host 'error was caught... You are safe ;)' -ForegroundColor Green
}
}
}
}
$logline = "Count: $Count - $($event.MessageData.Name): $changeType, $path"
write-host $logline -ForegroundColor $Event.MessageData.ForegroundColor
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcherNoError "Changed" -Action $action -MessageData #{Name='NoErrors';ForegroundColor='Yellow'}
Register-ObjectEvent $watcherWithErrors "Changed" -Action $action -MessageData #{Name='WithErrors';ForegroundColor='DarkMagenta'}
Register-ObjectEvent $watcherErrorsTryCatch "Changed" -Action $action -MessageData #{Name='WithErrorsTryCatch';ForegroundColor='Green'}
while ($true) {sleep 5}
$action = {
try {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
#Add-content "D:\log.txt" -value $logline
#write-host $logline
$targetdevice = Get-SmbOpenFile |
select clientusername, clientcomputername, path |
where {$_.Path -like 'E:\Data\Archive\_Do_Not_Delete_Or_Rename' }
$targetIP = $targetdevice.clientcomputername
$targetUser = $targetdevice.clientusername
Send-ToEmail -email "edu.bit.es#gmail.com" $targetIP
$targetUser
}
catch {
Write-Host 'an error was thrown :(... Fortunately, it was caught.'
}
}
Additional note:
Even if it was not the solution to your problem, you should still put a try / catch in that Action scriptblock as a terminating error will stop further processing on the next changes.

Either you'll need to use
Remove-Event $watcher "Changed"
at the end of the $Action scriptblock
OR
use
Unregister-Event $watcher "Changed"
Register-ObjectEvent $watcher "Changed -Action $action
at the end of the $Action scriptblock.

Related

PowerShell IO.FileSystemWatcher doesn't work after restart fileserver

I need a FileSystemWatcher on a network directory (fileserver)
The script works well but fails after a reboot from the fileserver.
How can I detect if the FSW is failing and restart the watcher if the fileserver is up and running again?
Code:
$destinations = #{"\\location1" = "c:\destination1"
"\\location2" = "c:\destination2"
}
foreach ($location in $destinations.Keys) {
$Watcher = New-Object IO.FileSystemWatcher -Property #{
Path = $location
Filter = "*.*"
IncludeSubdirectories = $false
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier $location -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$SI = $Event.SourceIdentifier
Write-Host "The file '$name' was $changeType at $timeStamp"
Write-Host $path
Move-Item $path -Destination $destinations[$SI] -Force -Verbose
}
}
RTFM!
There is Error event from FileSystemWatcher, and there is your case exactly described.
For example, if the object is monitoring changes in a remote directory and the connection to that directory is lost, the Error event is raised.
Solution found:
$destinations = #{"\\location1" = "c:\destination1"
"\\location2" = "c:\destination2"
}
foreach ($location in $destinations.Keys) {
$Watcher = New-Object IO.FileSystemWatcher -Property #{
Path = $location
Filter = "*.*"
IncludeSubdirectories = $false
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$action = {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$SI = $Event.SourceIdentifier
Write-Host "The file '$name' was $changeType at $timeStamp"
Write-Host $path
Move-Item $path -Destination $destinations[$SI] -Force -Verbose
}
$actionError = {
$Sender.EnableRaisingEvents = $false
$ip = $location -split "\\" | Where { $_ -ne "" } | Select -first 1
do {
Write-Host "Waiting for boot " + $ip
Start-Sleep -Seconds 5
} until (Test-Connection $ip -Quiet -Count 1)
$Sender.EnableRaisingEvents = $true
}
Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier $location -Action $action
Register-ObjectEvent $Watcher -EventName Error -SourceIdentifier $location+'ERROR' -Action $actionError
}

How to detect Folder change (renaming in particular) inside a folder using Powershell file watcher

I am using powershell to detect and record a folder change (created, renamed, deleted etc) inside a folder.
This parent folder receives subfolders from another location in format TEMP_XYZ. Once this folder is copied to this parent folder, the process automatically renames it to XYZ (Removed suffix TEMP_)
This change (rename) had to be detected and recorded in a log file as
\\test\folderwatch\XYZ was Renamed at 7/28/2021 2:03:00 PM
Folder TEMP_XYZ was renamed to XYZ
However, I am not able to achieve this as the code below only works on files. (txt,bmp, zip etc)
Any help is appreciated.
Code:
# specify the path to the folder you want to monitor:
$Monitorpath ="\\test\folderwatch"
$Path = $Monitorpath
# specify which files you want to monitor
$FileFilter = '*'
# specify whether you want to monitor subfolders as well:
$IncludeSubfolders = $true
# specify the file or folder properties you want to monitor:
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite
try
{
$watcher = New-Object -TypeName System.IO.FileSystemWatcher -Property #{
Path = $Path
#Filter = $FileFilter
IncludeSubdirectories = $IncludeSubfolders
NotifyFilter = $AttributeFilter
}
$action = {
# change type information:
$details = $event.SourceEventArgs
$Name = $details.Name
$FullPath = $details.FullPath
$OldFullPath = $details.OldFullPath
$OldName = $details.OldName
$ChangeType = $details.ChangeType
$Timestamp = $event.TimeGenerated
$LogDate = Get-Date -format "dd-MMMM-yy"
# save information to a global variable for testing purposes
# so you can examine it later
# MAKE SURE YOU REMOVE THIS IN PRODUCTION!**************************DO NOT USE FOR PROD**************************
$global:all = $details
$text = "{0} was {1} at {2}" -f $FullPath, $ChangeType, $Timestamp
Write-Host ""
Write-Host $text -ForegroundColor DarkYellow
Add-content "\\test\folder_watch_logs\watchlog_$LogDate.txt" -value $text
switch ($ChangeType)
{
'Changed' { "CHANGE" }
'Created' { "CREATED"}
'Deleted' { "DELETED"
Write-Host "Deletion Handler Start" -ForegroundColor Gray
Start-Sleep -Seconds 4
Write-Host "Deletion Handler End" -ForegroundColor Gray
}
'Renamed' {
# this executes only when a file was renamed
$text = "Folder {0} was renamed to {1}" -f $OldName, $Name
Write-Host $text -ForegroundColor Yellow
Add-content "test\folder_watch_logs\watchlog_$LogDate.txt" -value $text
}
# any unhandled change types surface here:
default { Write-Host $_ -ForegroundColor Red -BackgroundColor White ;
Add-content "test\folder_watch_logs\watchlog_$LogDate.txt" -value $_ }
}
}
$handlers = . {
Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Deleted -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $action
}
# monitoring starts now:
$watcher.EnableRaisingEvents = $true
$LogDate = Get-Date -format "dd-MMMM-yy"
Write-Host "Watching for changes to $Path"
Add-content "test\folder_watch_logs\watcherstatus.txt" -value "Watching for changes to $Path"
# since the FileSystemWatcher is no longer blocking PowerShell
# we need a way to pause PowerShell while being responsive to
# incoming events. Use an endless loop to keep PowerShell busy:
do
{
Wait-Event -Timeout 1
# write a dot to indicate we are still monitoring:
#Write-Host "." -NoNewline
} while ($true)
}
finally
{
# stop monitoring
$watcher.EnableRaisingEvents = $false
# remove the event handlers
$handlers | ForEach-Object {
Unregister-Event -SourceIdentifier $_.Name
}
$handlers | Remove-Job
# properly dispose the FileSystemWatcher:
$watcher.Dispose()
$LogDate = Get-Date -format "dd-MMMM-yy"
Write-Warning "Event Handler disabled, monitoring ends."
Add-content "test\folder_watch_logs\watcherstatus.txt" -value "Event Handler disabled, monitoring ends."
}
//Jagbir
Adjust your watcher's NotifyFilter so that it is looking at directory names
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite, [IO.NotifyFilters]::DirectoryName
or if you are only interested in changes in directory names only specify
$AttributeFilter = [IO.NotifyFilters]::DirectoryName
If you are only interested in the renaming events do not register the others
$handlers = . {
# Remove Changed, Created, and Deleted if they are of no concern
# Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action
# Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action
# Register-ObjectEvent -InputObject $watcher -EventName Deleted -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $action
}

How to make a io.systemfileswatcher run indefinitely

I have a script that calls two io.systemfile watchers which I would like to run 24x7 so that it can detect data that needs to be processed and automatically hand it off to a script that processes it (and uploads the result to the network):
Function Report1Watcher{
param ($folder)
$filter = "*vac*.csv"
$watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $true
EnableRaisingEvents = $true
InternalBufferSize = 65536
}
Write-Host "Watching $folder for creation of $filter files..."
$changeAction = {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$Actionpath = Split-Path $Path
write-host $Actionpath " Ready for Report1"
Report1 $Actionpath
write-host $Actionpath " Report1 Generated"
}
Register-ObjectEvent $Watcher -EventName "Created" -Action $changeAction
}
Function Report2Watcher{
param ($folder)
$filter = "*4.4*"
$watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $true
EnableRaisingEvents = $true
InternalBufferSize = 65536
}
Write-Host "Watching $folder for creation of $filter files..."
$changeAction = {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$Actionpath = Split-Path $Path
$Actionpath = Split-Path $Actionpath
write-host $Actionpath " Ready for Report2"
Report2 $Actionpath
write-host "Report2 Generated"
}
Register-ObjectEvent $Watcher -EventName "Created" -Action $changeAction
}
Function Watchmaster{
Report1Watcher $Testdir
Report2Watcher $Testdir
}
Watchmaster
I am thinking of turning it into a windows service, but would it be able to run 24x7 as is? I'm not sure where I could add an infinite loop without creating infinite io.systemfilewatchers. I know as a PS script that powershell would need to constantly be running. But if I created a batch file that runs this and set it up as a service with nssm would this accomplish what I need?

Scan specific sub-folder of every folder in powershell

I have this script which is working fine
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\folder_to_scan\"
$watcher.Filter = "*.nrrd"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
$action =
{
$path = $Event.SourceEventArgs.FullPath
Write-Host "The file '$path' was $changeType at '$(Get-Date)'" -fore green
}
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 3600}
But in my C:\folder_to_scan\ I have a lot of sub-directories like 00123, 00245, 56002 ... And inside each of them I have \THE_DIRECTORY_TO_SCAN
So I tried this $watcher.Path = "C:\folder_to_scan\*\THE_DIRECTORY_TO_SCAN\" and $watcher.Path = "C:\folder_to_scan\[0-9]*\THE_DIRECTORY_TO_SCAN\"
But this is not working. Is it possible to use wildcard in this situation?
If not, how to use multi path with FileSystemWatcher?
Because I figured out that we can use this
Resolve-Path "C:\folder_to_scan\*\THE_DIRECTORY_TO_SCAN\" | Select -ExpandProperty Path
I'm not familiar with FileSystemWatcher so I am assuming you just need to feed in the folder paths to $watcher.Path
I'm also assuming your code works already.
try using Get-ChildItem and use a for loop:
$watcher = New-Object System.IO.FileSystemWatcher
Get-ChildItem "C:\folder_to_scan\" -Recurse | ? { $_.PSIsContainer -and $_.Name.EndsWith("THE_DIRECTORY_TO_SCAN")}| %{
$watcher.Path = $_.FullName #change this
$watcher.Filter = "*.nrrd"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
}
$action =
{
$path = $Event.SourceEventArgs.FullPath
Write-Host "The file '$path' was $changeType at '$(Get-Date)'" -fore green
}
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 3600}
Ok so to clarify what i was talking about earlier. You are only repeating your wait-function. To show you what i mean :
echo "This would be your code running."
while ($true) {sleep -second 3}
This would print the sentence once and then keep waiting for eternity for 3 seconds each time $true is true.
What you need to do is :
do{
echo "This would be your code running."
sleep -seconds 3
} while($true)
This will print the sentence every 3 seconds.
Or in your case, run your watcher every 3 seconds.
I didn't find a perfect solution so I did this
if ( $path -like '*\THE_DIRECTORY_TO_SCAN\*' )
{ Write-Host "File created in \THE_DIRECTORY_TO_SCAN" -fore green }
else
{ Write-Host "File created NOT in \THE_DIRECTORY_TO_SCAN" -fore red }

New-Object System.IO.FileSystemWatcher multiple servers

I've got a script based on New-Object System.IO.FileSystemWatcher that watches a folder on my home file server for new files, and runs an external app, but now i'm looking to expand on the usage of New-Object System.IO.FileSystemWatcher, to monitor a set of multiple servers (input from a .csv).
I've got the below code working as far as detecting the events, but it generates an alert for a new file multiple times when new files are detected. Any idea how I could get it just generate one alert? I'm thinking it's how my loop is structured?
Any help appreciated!
$Servers = import-csv "C:\Scripts\Servers.csv"
while($true) {
ForEach ($Item in $Servers) {
# Unregister-Event $changed.Id -EA 0
$Server = $($Item.Server)
write-host "Checking \\$Server\c$\Scripts now"
#$folder = "c\Scripts"
$filter = "*.html"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\$Server\c$\Scripts\"
$watcher.Filter = "*.html"
$watcher.IncludeSubdirectories = $False
$watcher.EnableRaisingEvents = $true
$created = Register-ObjectEvent $watcher "Created" -Action {
write-host "A new file has been created on $Server $($eventArgs.FullPath) -ForegroundColor Green
}
} #ForEach
write-host "Monitoring for new files. Sleeping for 5 seconds"
Start-Sleep -s 5
} #While
Here is the single server version of my script, basically, i want to do the same thing, except running against a bunch of servers:
$SleepTimer = 15
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\FILESERVER\NEWSTUFF\"
$watcher.Filter = "*.html"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER A EVENT IS DETECTED
$action = {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$changeType, $path"
write-host "$LogLine created"
**RUN EXTERNAL PROGRAM HERE**
add-content -Value $LogLine -path "\\Fileserver\Log.txt"
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED + SET CHECK FREQUENCY
$created = Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {
write-warning "no new files detected. Sleeping for $SleepTimer seconds ..."
start-sleep -s $SleepTimer
}
I think each time the while loop is executing , new file system watcher is created which is generating the multiple alert .which is not happening in your single server version of script
can you check this :
$Servers = import-csv "C:\Scripts\Servers.csv"
ForEach ($Item in $Servers) {
# Unregister-Event $changed.Id -EA 0
$Server = $($Item.Server)
write-host "Checking \\$Server\c$\Scripts now"
#$folder = "c\Scripts"
$filter = "*.html"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\$Server\c$\Scripts\"
$watcher.Filter = "*.html"
$watcher.IncludeSubdirectories = $False
$watcher.EnableRaisingEvents = $true
$created = Register-ObjectEvent $watcher "Created" -Action {
write-host "A new file has been created on $Server $($eventArgs.FullPath)" -ForegroundColor Green
}
}
while ($true) {
write-host "Monitoring for new files. Sleeping for 5 seconds"
Start-Sleep -s 5
} #While