FileSystemWatcher works in the PowerShell ISE but not when run - powershell

I want to monitor a folder and move files that match certain criteria, so I'm trying to use the FileSystemWatcher.
I have a function that will be called with each new file:
function ProcessFile()
{
param ([string]$filename)
Write-Host "Processing file '$filename' to $destination"
}
And then I set up a FSW:
Write-Host "Watching $source for new files..."
$fsw = New-Object IO.FileSystemWatcher $source, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action
{
ProcessFile $Event.SourceEventArgs.FullPath
}
That works fine when I run it from the ISE, and any files I drop into the watched folder are correctly tracked, but if I start a PowerShell window and run the script with .\FileWatch.ps1 then nothing happens.
I see the "watching ..." message, but never see a "processing..." message
Here's the full script that works in the ISE but not in a shell...
$source = 'D:\Dev\PowerShell\FileWatch\Test\Source'
$filter = '*.*'
$destination = 'D:\Dev\PowerShell\FileWatch\Test\Found\'
function ProcessFile()
{
param ([string]$filename)
Write-Host "Processing file '$filename' to $destination"
}
Write-Host "Watching $source for new files..."
$fsw = New-Object IO.FileSystemWatcher $source, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
ProcessFile $Event.SourceEventArgs.FullPath
}

The problem is that your function ProcessFile isn't loaded in powershell session.
Try loading you script in this way:
. .\myscript.ps1
In this way your code in my system works!
Read about Dot Sourcing a script in powershell.

Related

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
}

FileSystemWatcher not firing events till PowerShell closes

I have a script (below) that watches a folder that the .ps1 script sits in.
When a file is created it fires a .bat file to do a job.
Initially it would run and close immediately.
So I added '''Start-Sleep -s 50'''
It works but it only triggers the .bat launch when the PowerShell window closes.
(As I don't know how long it will be till a file turns up in the folder, this is kind of useless).
Ideally I could do with the .bat file launching as soon as the new file is created, which in turn then closes the PowerShell window
$configFilePath = $PSScriptRoot
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $configFilePath, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp" -fore green
Out-File -FilePath c:\temp\log\Filelog.txt -Append -InputObject "The file '$name' was $changeType at $timeStamp"
Set-Location "$PSScriptRoot"
Start-Process "$PSScriptRoot\PS_Run.bat"
}
Start-Sleep -s 50
You can replace Start-Sleep with:
Wait-Event -SourceIdentifier FileCreated
Then you need to add an exit command to your watcher like this:
Start-Process "$PSScriptRoot\PS_Run.bat"
exit
}
Since you cannot exit the console from the filewatcher, you can do this instead:
$configFilePath = $PSScriptRoot
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $configFilePath, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp" -fore green
Out-File -FilePath c:\temp\log\Filelog.txt -Append -InputObject "The file '$name' was $changeType at $timeStamp"
Set-Location "$PSScriptRoot"
Start-Process "$PSScriptRoot\PS_Run.bat"
}
Wait-Event -SourceIdentifier FileCreated -Timeout 50 # or no of seconds before file shows up.
When run as a scheduled task, this will execute the bat file as soon as a new file is created and close the console when the timeout is reached.

Powershell: copy-item deletes file

I have a client that removes/writes files every 10 seconds on my laptop. I also have a Powershell script that is supposed to copy files whenever they are changed, to a network location.
Now to the problem: sometimes, quite often, one or two of the files on the remote network location gets deleted. But I have nothing in the script that is supposed to delete any file, only copy.
How come this happens? See the script below:
$block = {
function CreateCopyFile
{
param ($message, $event)
# function to call when event is raised
# do a robocopy or whatever
$path = $Event.SourceEventArgs.FullPath
$targetpath = $targetFolder + "/" + $Event.SourceEventArgs.Name
Copy-Item $path $targetFolder
}
$watchedFolder = "C:\Users\test\Documents\folder"
$targetFolder = "\\TRICASTER-MINI\DataLink Watch Folder"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $watchedFolder
Register-ObjectEvent -InputObject $watcher -EventName Created -SourceIdentifier File.Created -Action { CreateCopyFile "Created" $event }
Register-ObjectEvent -InputObject $watcher -EventName Changed -SourceIdentifier File.Changed -Action { CreateCopyFile "Changed" $event }
}
$encodedBlock = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($block))
Start-Process PowerShell.exe -verb Runas -argumentlist '-WindowStyle Hidden', '-NoExit', '-EncodedCommand', $encodedBlock

Unregister a registered filewatcher event does not work

I want to watch a folder with powershell and I am a PS beginner.
That script works ONE time when I start the script.
But when I have to restart the script again because I changed some script code I get this error message:
Cannot subscribe to the specified event. A subscriber with the source identifier 'FileChanged' already exists.
I tried:
this at the top of the script:
Unregister-Event -SourceIdentifier FileChanged
does not work.
How do I correctly unregister the event so I can run my script as often I want and the previously registered event is disposed?
CODE
$folder = "C:\temp"
$Watcher = New-Object IO.FileSystemWatcher $folder -Property #{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onChanged = Register-ObjectEvent $Watcher Changed -SourceIdentifier FileChanged -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp"
Write-Host $path
#Move-Item $path -Destination $destination -Force -Verbose
}
Ok, looking at what your trying to achieve... to answer your original question, you need to do the following to unregistered the event.
Get-EventSubscriber -SourceIdentifier "filechanged" | Unregister-Event
I have to ask why are you having to make 1000 adjustments to the code. If you are trying to register 1000 different events to be monitored it would make more sense to loop and increment a variable using the ++ modifier.
I have achieved this already if this is what your tying to accomplish and can share some code if you need it.

Trigger script when new folder has been added to a location

I'm automating a process and already made a powershell script for that. Now I need to make something which will call that script everytime a new folder has been added to a specific location i.e. a new build is dropped.
What should I use for this. Is WCF too much? And if not, got any leads for that? Any useful links.
Or is another powershell script better for that?
Keep in mind I need to check subfolders too.
Thanks.
Personnaly I'ld use System.IO.FileSystemWatcher
$folder = 'c:\myfoldertowatch'
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folder, $filter
$fsw.IncludeSubdirectories = $true
$fsw.NotifyFilter = [IO.NotifyFilters]'DirectoryName' # just notify directory name events
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { ... do my stuff here } # and only when is created
Use this to stop watching event
Unregister-Event -SourceIdentifier FileCreated
Try this:
$fsw = New-Object System.IO.FileSystemWatcher -Property #{
Path = "d:\temp"
IncludeSubdirectories = $true #monitor subdirectories within the specified path
}
$event = Register-ObjectEvent -InputObject $fsw –EventName Created -SourceIdentifier fsw -Action {
#test if the created object is a directory
if(Test-Path -Path $EventArgs.FullPath -PathType Container)
{
Write-Host "New directory created: $($EventArgs.FullPath)"
# run your code/script here
}
}