2

I am working on a Powershell Script to monitor a folder and when a new item is created the script needs to copy that file to another folder.

The issue I'm experiencing is that when I execute it in Powershell ISE it works perfectly but when executing it on Powershell it only works for the period of time that Powershell window is open (> 1 second).

I tried putting the sleep command at the end, and found that only when the script got ended the action is taken, in this case, when I press CTRL+C to stop the script in Powershell, the actions that should have been taken when I created the items are executed all together.

Don't know if I am doing something wrong or just misunderstanding something.

Here is the script i am using to test it:

$Watcher = New-Object System.IO.FileSystemWatcher
$Watcher.path = "\\192.168.5.127\data\TestWatcher"
$Destination = "C:\TestWatcher"
$Watcher | Get-Member -MemberType Event
$Watcher.EnableRaisingEvents = $true

$action = {
    $path = $event.SourceEventArgs.FullPath
    $name = $event.SourceEventArgs.Name
    $changetype = $event.SourceEventArgs.ChangeType
    Write-Host "File $name at path $path was $changetype at $(get-date)"
    Copy-Item $Watcher.path $Destination
}

Register-ObjectEvent $Watcher 'Created' -Action $action

Any help or advice would be appreciated.

Best Regards,

7
  • 1
    Try using -NoExit when creating the parent process. powershell.exe -File path.ps1 -NoExit Commented May 26, 2022 at 12:41
  • Hello Santiago, I have tried it with Windows Run putting these "powershell.exe -File path.ps1 -NoExit" but had the same result, the powershell window just opens for a moment and closes back Commented May 26, 2022 at 13:30
  • The only thing i been able to is to put a ``` while ($true){ } ``` at the end of the script. This keeps the powershell window open and makes the script work, if someone have an idea for a better option i would be glad, i don't think is the best way to have this window in the screen and if someone closes it the script would terminate. Tried also the option -WindowStyle Hidden but didn't worked. Commented May 26, 2022 at 13:38
  • 1
    while($true) will burn cpu cycles, maybe try wait-event 'forever' at the end Commented May 26, 2022 at 20:18
  • 1
    also note that the reason it works in the ISE is because the environment isn't disposed, so the object that the event handler is attached to is never given over to the garbage handler. Commented May 26, 2022 at 20:49

1 Answer 1

5

Gregor y provided the crucial hint in a comment:

To ensure that your script keeps processing events indefinitely, you can use a Wait-Event call at the end of your script to indefinitely wait for events that will never arrive, which keeps your script running, but - unlike Start-Sleep - does not block processing of events via the script block passed to Register-ObjectEvent's -Action parameter:

$Watcher = New-Object System.IO.FileSystemWatcher
$Watcher.path = "\\192.168.5.127\data\TestWatcher"
$Destination = "C:\TestWatcher"
$Watcher.EnableRaisingEvents = $true

$action = {
    $path = $event.SourceEventArgs.FullPath
    $name = $event.SourceEventArgs.Name
    $changetype = $event.SourceEventArgs.ChangeType
    Write-Host "File $name at path $path was $changetype at $(get-date)"
    Copy-Item $Watcher.path $Destination
}

# Register the event with a self-chosen name passed to -SourceIdentifier
# and an -Action script block.
$eventJob = 
  Register-ObjectEvent $Watcher Created -SourceIdentifier FileWatcher -Action $action

# Now wait indefinitely for an event with the same source identifier to arrive.
# NONE will ever arrive, because the events are handled via the -Action script block.
# However, the call will prevent your script from exiting, without
# blocking the processing of events in the -Action script block.
# Use Ctrl-C to exit the script; the `finally` clause ensures cleanup.
try {
  Wait-Event -SourceIdentifier FileWatcher
} 
finally {
  # Clean up: Remove the event job, which also unregisters the event.
  $eventJob | Remove-Job -Force 
}

Alternatively, make do without an -Action script block and process events in a Wait-Event loop:

$Watcher = New-Object System.IO.FileSystemWatcher
$Watcher.path = "\\192.168.5.127\data\TestWatcher"
$Destination = "C:\TestWatcher"
$Watcher.EnableRaisingEvents = $true

# Register the event with a self-chosen name passed to -SourceIdentifier
# but WITHOUT an -Action script block.
Register-ObjectEvent $Watcher 'Created' -SourceIdentifier FileWatcher

# Now use Wait-Event with the chosen source identifier to
# to indefinitely receive and then process the events as they 
# become available.
try {
  while ($event = Wait-Event -SourceIdentifier FileWatcher) {
    $path = $event.SourceEventArgs.FullPath
    $name = $event.SourceEventArgs.Name
    $changetype = $event.SourceEventArgs.ChangeType
    Write-Host "File $name at path $path was $changetype at $(Get-Date)"
    Copy-Item $Watcher.path $Destination
    $event | Remove-Event # Note: Events must be manually removed.
  }
}
finally {
  # Clean up.
  Unregister-Event -SourceIdentifier FileWatcher
}

Alternative with event-polling loop that permits foreground activity:

You can adapt the above to use a polling approach, by using an unconditional infinite loop, and replacing Wait-Event with a Get-Event call inside the loop body, followed by short sleep intervals (via Start-Sleep), which allows you to perform foreground activities while no events are being received:

$Watcher = New-Object System.IO.FileSystemWatcher
$Watcher.path = "\\192.168.5.127\data\TestWatcher"
$Destination = "C:\TestWatcher"
$Watcher.EnableRaisingEvents = $true

# Register the event with a self-chosen name passed to -SourceIdentifier
# but WITHOUT an -Action script block.
Register-ObjectEvent $Watcher 'Created' -SourceIdentifier FileWatcher

# Now enter an infinite loop that uses Get-Event to poll for
# available events and processes them.
# If none are available, foreground activity can be performed
# (which should be short-lived, otherwise event retrieval is blocked).
try {
  while ($true) {
    if ($event = Get-Event | Where-Object SourceIdentifier -eq FileWatcher) {
      $path = $event.SourceEventArgs.FullPath
      $name = $event.SourceEventArgs.Name
      $changetype = $event.SourceEventArgs.ChangeType
      Write-Host "File $name at path $path was $changetype at $(Get-Date)"
      Copy-Item $Watcher.path $Destination
      $event | Remove-Event # Note: Events must be manually removed.
    } else { # No event available.
      # Perform (short-lived) foreground activities here.
      Write-Host -NoNewline .
      Start-Sleep -Milliseconds 500 # Sleep a little, to avoid a tight loop.
    }
  }
}
finally {
  # Clean up.
  Unregister-Event -SourceIdentifier FileWatcher
}

Note:

  • Due to a bug present up to at least PowerShell v7.3.x, Get-Event doesn't work with the -SourceIdentifier parameter, hence the Get-Event | Where-Object SourceIdentifier -eq FileWatcher workaround above.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.