DotNetZip ExtractProgress events in PowerShell - powershell

I'm extracting a ZIP file in PowerShell by using the DotNetZip library, and I want to display progress. I'm using the following code:
try {
$zip = [Ionic.Zip.ZipFile]::Read($ZipFileName)
Register-ObjectEvent `
-InputObject $zip `
-EventName ExtractProgress `
-SourceIdentifier ExtractProgress `
-Action {
[Console]::Beep()
Write-Host $Sender
Write-Host $SourceEventArgs
} | Out-Null
$zip.ExtractAll($Destination, 'OverwriteSilently')
}
finally {
Unregister-Event -SourceIdentifier ExtractProgress
$zip.Dispose()
}
My problem is that I don't see the events (no beep, no Write-Host) until the very end. I'm expecting to see progress events during the process.
Initially, I thought it was because Register-ObjectEvent queued the events, but the PowerShell help says that -Action is invoked immediately, without the event being queued.
If I write the equivalent code in a C# console application, then I see the progress events as each file is extracted, as expected, which means that (as far as I can tell) DotNetZip is doing the right thing. Note that the events are raised on the same thread that called ExtractAll.
What am I doing wrong?
(Windows 7 x64, PowerShell 2.0, configured to use .NET 4.0)

Related

How do you automatically clean up all event subscriptions made in a PowerShell script when that script exits?

I'm setting up a FileSystemWatcher to watch a file for changes. This works. I also want to keep the script that sets this up running until the user manually terminates the script. This also works. However, I'd also like the event subscription on the FileSystemWatcher to get automatically cleaned up when the script exits (either normally or abnormally). This doesn't work, because event subscriptions are part of the session, and the script doesn't have its own session.
I tried creating a new session object inside the script and using it for the watcher setup and event registration, which seemed to do a great job cleaning up the event subscription on script termination, but it also seemed to cause all my console activity to get swallowed up in that child session.
How can I make it so that whenever the script exits (normally or abnormally), the event subscription is cleaned up automatically? (And doing this while maintaining visibility of my console output.)
In case the context matters, this is a simple ZIP file build script. I'm trying to add a "watch mode" to it so that when the ZIP is updated by another app, the ZIP is decompressed back to the folder from which it was created. So this script is meant to be executed from a PowerShell command line that remains active and is possibly used for other things before and after this script runs. In other words, the mighty hammer of Get-EventSubscriber | Unregister-Event is potentially a little too mighty, in addition to being another command that the script user would have to invoke on their own.
This is a condensed version of my script:
$watcher = New-Object System.IO.FileSystemWatcher ".", $fileName -Property #{
NotifyFilter = [IO.NotifyFilters]::LastWrite
}
Register-ObjectEvent $watcher -EventName Changed -Action {
Write-Host "File change detected."
# other things, scripts, etc
}
$watcher.EnableRaisingEvents = $true
Write-Host "Press Ctrl+C to stop watching the file."
while ($true)
{
if ([Console]::KeyAvailable)
{
$keyInfo = [Console]::ReadKey($true)
if ($keyInfo.Modifiers -eq [ConsoleModifiers]::Control -and $keyInfo.Key -eq [ConsoleKey]::C)
{
Exit
}
}
else
{
Start-Sleep 0.5
}
}
Try the following:
$watcher = New-Object System.IO.FileSystemWatcher $pwd, $fileName -Property #{
NotifyFilter = [IO.NotifyFilters]::LastWrite
}
# Register with a self-chosen source identifier.
$evtJob = Register-ObjectEvent -SourceIdentifier fileWatcher $watcher -EventName Changed -Action {
Write-Host "File change detected."
# other things, scripts, etc
}
$watcher.EnableRaisingEvents = $true
Write-Host "Press Ctrl+C to stop watching the file."
try {
# This blocks execution indefinitely while allowing
# events to be processed in the -Action script block.
# Ctrl-C aborts the script by default, which will execute
# the `finally` block.
Wait-Event -SourceIdentifier fileWatcher
}
finally {
# Clean up the event job, and with it the event subscription.
# Note: If the -Action script block produces output, you
# can collect it with Receive-Job first.
$evtJob | Remove-Job -Force
}

VSCode & Powershell: How to stop script and unregister object?

I have a snippet that works fine. It registers an objectevent as follows (snippet):
Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier FileCreated -Action {
It works fine, but I can't seem to get the script to fully stop and unregister. When I use "normal" methods to stop code (listed below), when I rerun the code (via F5) I get error:
Register-ObjectEvent : Cannot subscribe to the specified event. A subscriber with the source identifier 'FileCreated' already exists.
The only way I can unregister it (so far) is to terminate the powershell.exe process, which also kills the powershell run time within VSCode (throwing errors, forcing a manual restart).
I start the code by hitting F5
To stop the code, I have tried:
Shift-F5
Ctrl-C (in the powershell terminal area)
Nothing works except as described above.
What am I missing?
For debugging purposes only, where you often force-stop your script, there are 2 options. Either run the unregister-Event line manually so the event is unregistered or do add something like that near the top f your script. (at least before the Register-ObjectEvent )
Unregister-Event -SourceIdentifier FileCreated -EA 0
-EA 0 is the same as -ErrorAction SilentlyContinue and will prevent the script from throwing an error when you are on a new session and the event was never registered yet.
Note: This should not be in your production script and is only meant as a debugging help. (If you want to keep it on hand, I'd say comment it out at least on the prod. version of the script)
Your production version of the script should implement Unregister-Event properly though, something more akin to:
$FileWatcherReg = #{
InputObject = $Watcher
EventName = 'Created'
SourceIdentifier = 'FileCreated'
MessageData = #{WatchQueue = $WatchQueue; Timer = $WatchTimer }
Action = {
if ($null -ne $event) {
Write-Host "Path: $($Event.SourceArgs.FullPath) - ($($Event.Timegenerated))"
}
}
}
Register-ObjectEvent #FileWatcherReg
while ($true) {
Start-Sleep -Milliseconds 100
# Logic to exit the loop at some point.
}
# Exit script logic ...
Unregister-Event -SourceIdentifier FileCreated
Similar to Sage's example above, is this larger example. It structures the event clearly, and shows how by keeping the script running, you can then cancel the script, which will trigger the cleanup block.
So there is something to be said for this approach. (However, I continue to see the whole Register-ObjectEvent as kind of a TSR... your code stays resident... forever... waiting for the relevant event....)
The example:
https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/using-filesystemwatcher-correctly-part-2#

Keep PowerShell process alive without interrupting asynchronous event handlers

Summary
I'd like to know the best way to keep a PowerShell script running, without putting the main thread to sleep. Using Start-Sleep to keep a script alive has the side effect of blocking asycnhronous event handlers that are running on the same thread.
Example Code
Consider the following script, which demonstrates this blocking in action.
$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action { Write-Host -ForegroundColor Blue hi }
$Timer.Start()
Start-Sleep -Seconds 5
When running this script, you'll see that the Timer is blocked from executing its handlers until the Start-Sleep command has completed. However, you'll also notice that the events are queued up in the background, and all fire in quick succession once the main thread is released.
Also, the script exits once the Start-Sleep command has finished running.
Question: How do I keep the PowerShell process / script running, without blocking event handlers?
I asked a similar question on the PowerShell Polaris repository on GitHub about a year ago. The answer for that specific module is to use the PowerShell Wait-Event command.
Using the Wait-Event command, with a random -SourceIdentifier will block further execution of the main thread, without blocking event handlers from executing.
$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action { Write-Host -ForegroundColor Blue hi }
$Timer.Start()
Wait-Event -SourceIdentifier SomeEventIdThatWillNeverExist
Conditional Termination
If the script has "completed" (whatever that means to the author), and the registered event handlers wish to terminate the script, they can do so.
The -Action event handler on the Timer, in this example, could optionally fire an event with the SomeEventIdThatWillNeverExist as the value for its -SourceIdentifier parameter, using the built-in New-Event command. Otherwise, an outside could forcefully terminate the process when appropriate.
$Timer = [System.Timers.Timer]::new(500)
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -Action {
Write-Host -ForegroundColor Blue hi
if ((Get-Random -Maximum 100) -gt 95) {
New-Event -SourceIdentifier SomeEventIdThatWillNeverExist
}
}
$Timer.Start()
Wait-Event -SourceIdentifier SomeEventIdThatWillNeverExist
🚨 In an interactive PowerShell session, the Timer will continue to run after the script has completed execution. You would need to invoke the script using a separate PowerShell process (ie. pwsh -File timer.ps1).
At the moment, this is the best answer that I am aware of. However, if there's a better way of accomplishing this, I'm open to new answers.

Register-ObjectEvent Event Subscription Uptime

We have a data dump come from an external entity on a daily basis. Once we receive the file, a Object Event Listener triggers, moves the file into two separate locations (one live, one backup) and then calls a MS SQL stored procedure to import the file into the database.
The process and my script seem to function perfectly fine. The file moves, and the SQL is executed. However, every morning when I check to see if it triggered, nothing triggered. I check for event-subscribers by calling Get-EventSubscriber and there are no listeners registered. Once I register the listeners and move the file into the input location, everything runs fine.
I understand that the Event Subscriptions do not persist through reboots, but how long do they stay open? is it possible that something is closing them? or do they time out?
$watchFolder = "\\server\c$\path\to\inbound\"
$filter = "*.txt"
$fsw = New-Object IO.FileSystemWatcher $watchFolder, $filter -Property #{
IncludeSubdirectories = $false
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileMonitor -Action {
$filePath = $Event.SourceEventArgs.FullPath
$fileName = $Event.SourceEventArgs.Name
Write-Host "Source File: $fileName"
$success = copyFile($fileName, $filePath) #custom function to copy file to multiple destinations
if ($success) {
Write-Host "Starting SQL_JOB_NAME Job"
Set-Location SQLSERVER:\SQL\server\DEFAULT\Databases\msdb\
$execute_import = "EXEC dbo.sp_start_job N'SQL_JOB_NAME';"
Invoke-Sqlcmd -SuppressProviderContextWarning -Query $execute_import
} else {
Write-Host "Something failed along the way: `r`n"
}
}
As silly as it seems, I learned here that calling Register-ObjectEvent if the PowerShell process is closed, the ObjectEvent is also unregistered.
Because the nature of the project required us to have an always-on file listener, we decided that Powershell was not the correct implementation for us.
Instead we implemented a Windows service using the .NET libraries, which are the same used here in PowerShell.
Thanks to PetSerAl for his prompt answer.

How to use Register-Objectevent to invoke a powershell script?

I was trying to execute powershell in parallel mode. I was advised to create an event which will trigger whenever there is a change in folder.
For ex: If a folder name called "test1" is created in a particular folder then my powershell script test1.ps1 should be triggered.
I was trying to get example for this with Register-Objevent , but i am not getting any good clue.
If you have implemented some thing like this , can you please guide me?
Updated:
When i create a file named test1.ps1 in the "D:\ParallelEvent\temp" folder, it should execute test1.ps1 powershell file. Following is the code for that
$ScriptLoc="D:\My_Scripts"
$watcher = New-Object System.IO.FileSystemWatcher -Property #{Path = 'D:\ParallelEvent\temp';Filter = '*.txt';NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'}
$CreatedAction =
{
$FilenamewithoutExtn=$(($event.sourceEventArgs.Name).Split('.')[0])
Start-Job $ScriptLoc\$FilenamewithoutExtn.ps1
Get-Job -State Running | Wait-Job
}
Register-ObjectEvent -InputObject $watcher -EventName Created -SourceIdentifier FileCreated -Action $CreatedAction
My test1.ps1 has following line
[system.windows.forms.messagebox]::show("Hello, Test1!")
But still it doesn't execute the test1.ps1 it seems. I did not get any messagebox. Am i missing any thing?
you are not getting messagebox because powershell jobs run as background process and does not show ui.
if you modify the test1.ps1 to a file redirection
"test " > C:\AnywhereYouWantToRedirectData\testagain.txt
you will be able to see that the test1.ps1 runs and redirected file gets created (testagain.txt).