# encoding: ascii
# api: powershell
# title: Invoke-BackgroundTimer
# description: This is an example of how to use Timers, Events, and Jobs together in a way that doesn’t block the event, but does block the host. See the Example for … an example. More later when I have time to think of them ;-)
# version: 0.1
# type: function
# author: Joel Bennett
# license: CC0
# function: Invoke-BackgroundTimer
# x-poshcode-id: 5560
# x-archived: 2015-01-31T20:33:28
# x-published: 2015-10-31T21:25:00
#
#
function Invoke-BackgroundTimer {
#.Synopsis
# An example of how to run a script repeatedly on a timer...
#.Example
# Invoke-BackgroundTimer {
# Get-Process Sublime* |
# Select-Object ProcessName, PagedMemorySize, PagedSystemMemorySize,
# @{ Name = "Time"; Expr = { Get-Date } }
# }
#
# Shows the memory footprint of sublime every second for 30 seconds
[CmdletBinding(DefaultParameterSetName="Milliseconds")]
param(
[Parameter(Position=0,Mandatory=$True)]
[ScriptBlock[]]$Action,
[Parameter(ParameterSetName="Milliseconds")]
[int]$TimerMilliseconds = 1000,
[Parameter(ParameterSetName="Milliseconds")]
[int]$LimitSeconds = "30",
[Parameter(ParameterSetName="TimeSpan")]
[TimeSpan]$Every = "0:0:1",
[Parameter(ParameterSetName="TimeSpan", Mandatory=$true)]
[TimeSpan]$For
)
if($PSCmdlet.ParameterSetName -eq "Timespan") {
$TimerMilliseconds = $Every.Milliseconds
$LimitMilliseconds = $For.Milliseconds
} else {
$LimitMilliseconds = $LimitSeconds * 1000
}
# The resolution of the timer matters:
$ProcessTimer = New-Object System.Timers.Timer $TimerMilliseconds
# I'm arbitrarily using a stopwatch as the limit to this task
# You could just count events instead ...
$StopWatch = New-Object System.Diagnostics.StopWatch
$MessageData = @{
Action = $Action
StopWatch = $StopWatch
Limit = $LimitMilliseconds
}
$Job = Register-ObjectEvent $ProcessTimer -SourceIdentifier LoopTimer -EventName Elapsed -MessageData $MessageData -Action {
# This is the exit condition, don't run it any more
if($Event.MessageData.StopWatch.ElapsedMilliseconds -ge $Event.MessageData.Limit) {
Unregister-Event LoopTimer
break
}
# If you want to be able to tell that something's happening ...
# You're going to want to write something to the screen in here...
Write-Progress "Processing" -SecondsRemaining (($Event.MessageData.Limit - $Event.MessageData.StopWatch.ElapsedMilliseconds) / 1000)
# This is the actual work. In our example, we're just monitoring a process memory footprint
$Event.MessageData.Action | %{ & $_ }
}
$ProcessTimer.Start()
$StopWatch.Start()
Write-Warning "If you stop the script using Ctrl+C at this point, you need to manually Unregister-Event LoopTimer"
# To avoid missing events sleep an order of magnitude less than the TimerResolution
while(!$Job.Finished.WaitOne($TimerMilliseconds/10)) {
$Job | Receive-Job
}
# Just to make sure the job is empty ...
$Job | Receive-Job
$Job | Remove-Job
# Clean up after ourselves
$ProcessTimer.Dispose()
$StopWatch.Stop()
}