New-Object System.IO.FileSystemWatcher multiple servers - powershell

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

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 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?

System.IO.FileSystemWatcher only notifies once on edited files

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.

External powershell scripts only run once when action called

I am trying to call an outside ps script to run every time files are created. The monitoring and logging of files work well, but the Invoke-Expression of running the external script only runs one time even if more files are created. How do I run the external script with every time a new file is created.
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "c:\mypath"
$watcher.Filter = "*.txt*"
$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 "C:\mypath\log.txt" -value $logline
Invoke-Expression (start powershell ("C:\MyOtherScript.ps1")) ###### This only runs one time even if file is changes and logged
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
EDIT: This got it working incase anyone find themselves here looking to solve the porblem
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "c:\mypath"
$watcher.Filter = "*.txt*"
$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 "C:\mypath\log.txt" -value $logline
Start-Process powershell -argument "C:\MyOtherScript.ps1"
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
#while ($true) {sleep 5}
Invoke-Expression is not recommend over using & due to security concerns. In your example, however, I'd suggest Start-Process:
Start-Process -FilePath 'powershell' -ArgumentList #('-File','"C:\MyOtherScript.ps1"')

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 }