Timer event in powershell not working in remote session - powershell

I have a remote PowerShell session from machine A to B. Requirement is to do a continuous check inside B. On a specific condition, I have to execute something and stop the timer. For that, I created a timer function. But that timer function is not starting.
The code that I have written is:
$timer = new-object timers.timer
$action = {
write-host "inside loop.."
# Condition to check comes here.
}
$timer.Enabled = $true
$timer.Interval = 3000 #3 seconds
Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier thetimer -Action $action
$timer.start()
I expect the output to be
inside loop..
inside loop..
inside loop..
but the actual output is:
Id----------------1
Name--------------thetimer
PSJobTypeName----------
State-------------NotStarted
HasMoreData-------False

Related

PowerShell - how to stop script processing elapsed events from Timer class

I'm writing a PowerShell script in Visual Studio Code which is intended to run a function for every tick of a timer. Here is the code:
$timer = New-Object Timers.Timer
$timer.Interval = 1000
$timer.Enabled = $true
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {Write-Host "Tick"}
The problem is that I don't seem to be able to stop the script using Ctrl+C which is what I would normally do, or indeed anything other than actually killing the terminal using the 'dustbin' icon. It just continues printing 'Tick'!
What's going on here? How would I stop this script gracefully?
When you subscribe an action to an event using Register-ObjectEvent, it can be unregistered using the Unregister-Event cmdlet:
$timer = New-Object Timers.Timer
$timer.Interval = 1000
$timer.Enabled = $true
Register-ObjectEvent -SourceIdentifier MyElapsedTick -InputObject $timer -EventName Elapsed -Action {Write-Host "Tick"}
<# ... #>
Unregister-Event -SourceIdentifier MyElapsedTick
To enumerate existing subscribers (for when you forget to specify a SourceIdenfitier for example), use Get-EventSubscriber:
# This will unregister all event subscribers
Get-EventSubscriber |Unregister-Event
$timer = [System.Timers.Timer]::new()
$timer.Interval = 5000
$timer.AutoReset = $false
$timer.Enabled = $true
1..20 | % {
if (!$timer.Enabled){
exit
}
write-host $_, $timer.Enabled
Start-Sleep -Milliseconds 1000
}

Why "Write-Host" works differently from PowerShell function and timer handler?

I have 1809 Windows 10 box with PowerShell Core 6.1.1
Given following code sample:
function Test() {
Write-Host "Test"
}
function Invoke-Test() {
$timer = New-Object System.Timers.Timer
$timer.AutoReset = $false
$timer.Interval = 1
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
Test
}
$timer.Enabled = $true
}
If I invoke "Test" function, I get "Test" output as expected:
But if I schedule invocation with a timer, command prompt is completely messed up:
I vaguely understand that it's something related to internal "readline" and console mechanic, but is it any way to produce newline output followed by a command prompt from a timer/handle in powershell?
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Timer.Test -Action {
Test
}
$timer.Enabled = $true
PS C:\> $Subscriber = Get-EventSubscriber -SourceIdentifier Timer.Test
PS C:\> $Subscriber.action | Format-List -Property *
The command property should hold the results of your “test” function.
The url below has detailed explanations.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-eventsubscriber?view=powershell-6

Processes in Powershell

I have got a problem.
1.The first situation
Process has already started.
And I want to do something after the current process is completed.
For example
$process=[System.Diagnostics.Process]::GetProcessesByName("notepad")
And
Register-ObjectEvent $process Exited -Action {}
But in this case, it's not working
How Can I Register Event "Exited"?
2. The Second situation
The Process has not yet been started.
How I can to wait for start process?)
First part is answered by Mathias R. Jessen with [System.Diagnostics.Process]::GetProcessesByName("notepad") | foreach {Register-ObjectEvent $_ Exited -Action { ... } }
For second part, the solution is below. Credit goes here PDQ.com - Register-ObjectEvent
You need to use WQL queries to monitor for process start event, then set up a query watcher that will fire an event in response of some process getting started, then listen with Register-ObjectEvent on that watcher. Code-copy-paste:
$Query = New-Object System.Management.WqlEventQuery "__InstanceCreationEvent", (New-Object TimeSpan 0,0,1), "TargetInstance isa 'Win32_Process'"
$ProcessWatcher = New-Object System.Management.ManagementEventWatcher $query
$Action = { New-Event "PowerShell.ProcessCreated" -Sender $Sender -EventArguments $EventArgs.NewEvent.TargetInstance }
register-objectEvent -InputObject $ProcessWatcher -EventName "EventArrived" -Action $Action

PowerShell async download completion event handler never gets executed

I'm trying to create a PowerShell script that would download posh-git and then install it automatically. I'd like to have the download happen asynchronously, preferably with the option to have a callback once the download has finished and/or be able to wait for it to complete (but executing code until you want to wait).
I created a function that takes the URL for download, the path where to save it and the callback function. Unfortunately the callback function never seems to be called:
function DownloadFileAsync([string] $url, [string] $destination, [ScriptBlock] $action = $null)
{
$web_client = New-Object System.Net.WebClient
if ($action)
{
Register-ObjectEvent -InputObject $web_client -EventName DownloadFileCompleted -Action $action | Out-Null
}
$web_client.DownloadFileAsync($url, $destination)
}
$download_finished =
{
# This is never reached
echo "Download finished"
}
DownloadFileAsync "https://github.com/dahlbyk/posh-git/archive/master.zip" "C:/posh-git-master.zip" $download_finished
How can I fix the callback never being called? Is there also a way to implement a way to wait, later on in the code, for the download to complete?
Output from event action is not printed on PowerShell host, but captured and saved in job, returned by Register-ObjectEvent:
$web_client = New-Object System.Net.WebClient
$Job = Register-ObjectEvent -InputObject $web_client -EventName DownloadStringCompleted -Action {
Write-Host 'Download completed'
$EventArgs.Result
}
$web_client.DownloadStringAsync('http://stackoverflow.com/q/39185082')
Now when download completed, you can use Receive-Job $Job to receive job results.
If you want to print something on PowerShell host, then you need to use Write-Host or Out-Host cmdlets.

How can I check for variable changes in a System.Windows.Form

I am displaying a form that runs another script after a button is clicked. I need to check for the state of the script completion so that I can update the text of the button.
$ButtonEmailInfo.Add_Click({
$ButtonEmailInfo.Text = "Sending info"
$ButtonEmailInfo.Enabled = $false
$Form.Refresh()
Write-Host("Running start-job")
$global:SendClicked = $true
$global:SJob = Start-Job -filepath ($path + "\Send-Info.ps1")
$ButtonEmailInfo.Text = "Info sent"
$Form.Refresh()
})
The problem with the above code is that the button text is set to "Info sent" before the called script Send-Info.ps1 has completed.
I can test $global:SJob.Finished too see if the script has completed but I'm not sure how you do this in a form. Is there the equivalent of an update() method that gets checked periodically?
Thanks,
Rich
You can use Register-ObjectEvent to handle the job and perform an action when it has completed. You would add the following right after you begin the job.
Edit You will need to setup a Timer object to update the Window and allow the thread to handle the Event output from Register-ObjectEvent.
$global:SJob = Start-Job -filepath ($path + "\Send-Info.ps1")
Register-ObjectEvent -InputObject $Global:SJob -EventName StateChanged -SourceIdentifier JobWatcher -Action {
#Job completed actions go here
Write-Host "Job Completed!"
# $Event.Sender is the actual job object that you can either remove or retrieve data from
#Perform cleanup of event subscriber
Unregister-Event -SourceIdentifier $Event.SourceIdentifier
Remove-Job -Name $Event.SourceIdentifier -Force
}
Here is an example that you can run to see it in action without using it in a form that notifies when completed and then proceeds to remove the job and the event subscription.
$SJob = Start-Job {start-sleep -seconds 10} -Name TESTJOB
Register-ObjectEvent -InputObject $SJob -EventName StateChanged -SourceIdentifier JobWatcher -Action {
#Job completed actions go here
Write-Host "Job $($Event.Sender.Name) Completed!"
#Remove job
Remove-Job $Event.Sender
#Perform cleanup of event subscriber and its job
Unregister-Event -SourceIdentifier $Event.SourceIdentifier
Remove-Job -Name $Event.SourceIdentifier -Force
}
Edit You will need to setup a Timer object to update the Window and allow the thread to handle the Event output from Register-ObjectEvent. Add the following lines to your UI to create the timer as the window loads and a timer stop when it closes.
$Window.Add_Loaded({
##Configure a timer to refresh window##
#Create Timer object
$Script:timer = new-object System.Windows.Threading.DispatcherTimer
#Fire off every 5 seconds
$timer.Interval = [TimeSpan]"0:0:1.00"
#Add event per tick
$timer.Add_Tick({
[Windows.Input.InputEventHandler]{ $Script:Window.UpdateLayout() }
})
#Start timer
$timer.Start()
If (-NOT $timer.IsEnabled) {
$Window.Close()
}
})
$Window.Add_Closed({
$Script:timer.Stop()
})