PowerShell File Watcher

PF 11 Reputation points
2022-08-18T20:04:30.883+00:00

Hello, I am looking for a solution how to, instead of waiting 15 seconds add code so my batch will run just after file is fully created (copied) - unlocked. Any help, please?

SET FOLDER TO WATCH

    $watcher = New-Object System.IO.FileSystemWatcher  
    $watcher.Path = "Z:\PolyWav"  
    $watcher.Filter = "*.wav"  
    $watcher.IncludeSubdirectories = $false  
    $watcher.EnableRaisingEvents = $true   
  
### ACTIONS AFTER AN EVENT IS DETECTED  
    $action = { $name = $Event.SourceEventArgs.Name  
                $wavename = $name -replace '.wav',''     
                 
                C:\PolyWav\Poly.bat "$wavename"  
              }      
### DECIDE WHICH EVENTS SHOULD BE WATCHED   
    Register-ObjectEvent $watcher "Created" -Action $action  
    while ($true) {sleep 15}  
Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,598 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Michael Taylor 55,841 Reputation points
    2022-08-18T20:20:39.46+00:00

    Unfortunately the FileSystemWatcher (FSW) is a pain to deal with in a compiled language and PS is going to make it that much harder. File system events don't work like you might think they do. You can google for all the discussions as to why.

    The net take away is that FSW events will get raised before you can actually do anything with the file in many cases and there is nothing you can do about it. Assume that you have a process P that is going to create or write to a file (there is no difference). P tells the file system to copy a file to some directory that you're monitoring. Depending on a variety of circumstances this could trigger a create and multiple update events or just a create event. The events occur as part of the call that P made so your code is getting called potentially before P even returns back from its call. Depending upon what you intend to do with the file and how P is interacting with the file there is a good chance P will still have a write lock on the file and therefore you won't be able to read it. There is no event to know when P has released its lock.

    The first challenge with FSW is that you cannot really do anything in the actual event handler. Firstly it is happening as part of a larger request and therefore needs to be fast. Secondly the other process still likely has the file open and may still be writing to it so you have no way of knowing what the state of the file is. In most implementations I've seen work, people push a notification to a queue that the event occurred and have a background thread running that then attempts to access the file and do the work periodically. Of course that background thread may not be able to do the work (because of locking) or may actually get notified multiple times (because a single write to a file can trigger multiple change events). So it can get tricky. You would need to deal with this in your PS script as well. The threading is the more difficult part as you'll have to have a queue that you monitor.

    The second challenge, as already mentioned, is that a single FS action can trigger multiple events. If the file is small you may just get a single create event. If it is large or the other process is writing it in chunks then you may also get one or more change events. Your watcher has no way of knowing what will happen and therefore when it is safe to proceed. There is no good solution here and is dependent upon what you are trying to do.

    Honestly I would try to solve this issue by looking at how I'm calling the thing that produces the file. Worst case is polling but even that can be problematic.

    3 people found this answer helpful.

  2. Johannes Kingma 26 Reputation points
    2025-01-02T21:39:17.7633333+00:00
    # Define the file path to monitor
    function Watch-File {
        [cmdletbinding()]
        Param (
            [string]
            $Path
        )
        # Create a FileSystemWatcher object
        $fileWatcher = New-Object System.IO.FileSystemWatcher
        $fileWatcher.Path = (Get-Item $Path).DirectoryName
        $fileWatcher.Filter = (Get-Item $Path).Name
        $fileWatcher.IncludeSubdirectories = $false
        $fileWatcher.EnableRaisingEvents = $true
    
        # Define the event action when a change is detected
    	# in this case toast a message
        $action = {
            $file = $Event.SourceEventArgs.FullPath
            $eventType = $Event.SourceEventArgs.ChangeType
            $message = "File '$file' has been $eventType"
            Show-Notification "Watch-File" $message 
        }
    
        # Register the event handler
        Register-ObjectEvent -InputObject $fileWatcher -EventName "Changed" -SourceIdentifier FileChanged -Action $action
    
        Write-Host "Monitoring file: $Path. Press Ctrl+C to stop."
    
        # Keep the script running
        try {
            while ($true) {
                Start-Sleep -Seconds 7
            }
        }
        finally {
            # Unregister the event handler when the script is stopped
            Unregister-Event -SourceIdentifier FileChanged
            $fileWatcher.EnableRaisingEvents = $false
        }
    }
    
    
    0 comments No comments

  3. Johannes Kingma 26 Reputation points
    2025-01-02T21:40:20.98+00:00
    # https://den.dev/blog/powershell-windows-notification/
    function Show-Notification {
        [cmdletbinding()]
        Param (
            [string]
            $ToastTitle,
            [string]
            [parameter(ValueFromPipeline)]
            $ToastText
        )
        [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
        $Template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
        $RawXml = [xml] $Template.GetXml()
        ($RawXml.toast.visual.binding.text|where {$_.id -eq "1"}).AppendChild($RawXml.CreateTextNode($ToastTitle)) > $null
        ($RawXml.toast.visual.binding.text|where {$_.id -eq "2"}).AppendChild($RawXml.CreateTextNode($ToastText)) > $null
        $SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument
        $SerializedXml.LoadXml($RawXml.OuterXml)
        $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml)
        $Toast.Tag = "PowerShell"
        $Toast.Group = "PowerShell"
        $Toast.ExpirationTime = [DateTimeOffset]::Now.AddMinutes(1)
        $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("PowerShell")
        $Notifier.Show($Toast);
    }
    Export-ModuleMember -Function Show-Notification
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.