{"id":5991,"date":"2008-06-10T19:15:00","date_gmt":"2008-06-10T19:15:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/powershell\/2008\/06\/10\/powershell-eventing-quickstart\/"},"modified":"2019-02-18T13:15:50","modified_gmt":"2019-02-18T20:15:50","slug":"powershell-eventing-quickstart","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell\/powershell-eventing-quickstart\/","title":{"rendered":"PowerShell Eventing QuickStart"},"content":{"rendered":"<p>&nbsp;<\/p>\n<p>The second CTP of PowerShell V2 (CTP2) introduces another new feature called PowerShell Eventing. PowerShell Eventing lets you respond to the asynchronous notifications that many objects support. We didn\u2019t get a chance to fully document these cmdlets in the CTP2, though, so here\u2019s a quick start and primer to help you explore the feature.<\/p>\n<h2>Discovering Events<\/h2>\n<p>Before playing with PowerShell eventing, you\u2019ll probably want to find an object that supports events. But how do you know what an object supports? You will most commonly discover this through API documentation (&#8220;To receive notifications, subscribe to the &lt;Foo&gt; event,&#8221;) but Get-Member has also been enhanced to show the events supported by an object:<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">PS &gt;$timer = New-Object Timers.Timer <br \/>PS &gt;$timer | Get-Member -Type Event <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">&nbsp;&nbsp; TypeName: System.Timers.Timer <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">Name&nbsp;&nbsp;&nbsp;&nbsp; MemberType Definition <br \/>&#8212;-&nbsp;&nbsp;&nbsp;&nbsp; &#8212;&#8212;&#8212;- &#8212;&#8212;&#8212;- <br \/>Disposed Event&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.EventHandler Disposed(System.Object, System.EventArgs) <br \/>Elapsed&nbsp; Event&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.Timers.ElapsedEventHandler Elapsed(System.Object, System.Timers.ElapsedEventArgs)<\/font><\/p>\n<\/blockquote>\n<h2>Subscribing to Events<\/h2>\n<p>Once you&#8217;ve determined the event name you are interested in, the Register-ObjectEvent cmdlet registers your event subscription in the system. The SourceIdentifier parameter helps identify events generated by this object, and lets you manage its event subscription:<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">PS &gt;Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed<\/font><\/p>\n<\/blockquote>\n<p>In addition to the Register-ObjectEvent cmdlet, additional cmdlets let you register for WMI events and PowerShell engine events.<font size=\"1\" face=\"Courier New\">&nbsp;<\/font><\/p>\n<p>The Get-PsEventSubscriber cmdlet shows us all event subscribers in the session:<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">PS &gt;Get-PSEventSubscriber <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">SubscriptionId&nbsp;&nbsp; : 4 <br \/>SourceObject&nbsp;&nbsp;&nbsp;&nbsp; : System.Timers.Timer <br \/>EventName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : Elapsed <br \/>SourceIdentifier : Timer.Elapsed <br \/>Action&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : <br \/>HandlerDelegate&nbsp; : <br \/>SupportEvent&nbsp;&nbsp;&nbsp;&nbsp; : False <br \/>ForwardEvent&nbsp;&nbsp;&nbsp;&nbsp; : False<\/font><\/p>\n<\/blockquote>\n<h2>Polling for Events<\/h2>\n<p>The simplest way to manage events is to simply subscribe to them, and occasionally check the event queue. When you are done with the event, remove it from the queue. The Get-PsEvent and Remove-PsEvent cmdlets let you do that:<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">PS &gt;Get-PsEvent <br \/>PS &gt;$timer.Interval = 2000 <br \/>PS &gt;$timer.Autoreset = $false <br \/>PS &gt;$timer.Enabled = $true <br \/>PS &gt;Get-PsEvent <br \/>PS &gt;Get-PsEvent <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">EventIdentifier&nbsp; : 11 <br \/>Sender&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : System.Timers.Timer <br \/>SourceEventArgs&nbsp; : System.Timers.ElapsedEventArgs <br \/>SourceArgs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : {System.Timers.Timer, System.Timers.ElapsedEventArgs} <br \/>SourceIdentifier : Timer.Elapsed <br \/>TimeGenerated&nbsp;&nbsp;&nbsp; : 6\/10\/2008 3:33:39 PM <br \/>MessageData&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : <br \/>ForwardEvent&nbsp;&nbsp;&nbsp;&nbsp; : False <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">PS &gt;Remove-PsEvent * <br \/>PS &gt;Get-PsEvent<\/font><\/p>\n<\/blockquote>\n<p>Most objects pack their interesting data in a class derived from &#8220;EventArgs.&#8221; To make working with these as easy as possible, the SourceEventArgs property is a shortcut to the first parameter in the event handler that derives from EventArgs. The SourceArgs parameter gives you full access to the event handler parameters, letting you deal with events that don&#8217;t follow the basic &#8220;Object sender, EventArgs e&#8221; pattern.<\/p>\n<h2>Waiting for Events<\/h2>\n<p>Polling for an event is tedious, since you usually want to wait until the event is raised before you do something. The Wait-PsEvent cmdlet lets you do that. Unlike synchronous objects on methods (i.e.: Process.WaitForExit(),) this cmdlet lets you press Control-C to halt the wait:<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">PS &gt;$timer.Interval = 2000 <br \/>PS &gt;$timer.Autoreset = $false <br \/>PS &gt;$timer.Enabled = $true; Wait-PsEvent Timer.Elapsed <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\"><em>&lt;2 seconds pass&gt;<\/em><\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">EventIdentifier&nbsp; : 12 <br \/>Sender&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : System.Timers.Timer <br \/>SourceEventArgs&nbsp; : System.Timers.ElapsedEventArgs <br \/>SourceArgs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : {System.Timers.Timer, System.Timers.ElapsedEventArgs} <br \/>SourceIdentifier : Timer.Elapsed <br \/>TimeGenerated&nbsp;&nbsp;&nbsp; : 6\/10\/2008 3:24:18 PM <br \/>MessageData&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : <br \/>ForwardEvent&nbsp;&nbsp;&nbsp;&nbsp; : False<\/font><\/p>\n<\/blockquote>\n<p>Once you&#8217;ve finished working with the event, remove it from the queue.<\/p>\n<h2>Asynchronous Processing of Events<\/h2>\n<p>While waiting for an event is helpful, you usually don&#8217;t want to block your script or shell session just waiting for the event to fire. To support this, the Register-*Event cmdlets support an -Action scriptblock. PowerShell will invoke that scriptblock in the background when your event arrives. These actions invoke in their own module \u2014 they can get and set $GLOBAL variables, but regular variable modifications happen in their own isolated environment.<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">function DoEvery <br \/>{ <br \/>&nbsp;&nbsp; param([int] $seconds,[ScriptBlock] $action ) <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">&nbsp;&nbsp; $timer = New-Object System.Timers.Timer <br \/>&nbsp;&nbsp; $timer.Interval = $seconds * 1000 <br \/>&nbsp;&nbsp; $timer.Enabled = $true <br \/>&nbsp;&nbsp; Register-ObjectEvent $timer &#8220;Elapsed&#8221; -SourceIdentifier &#8220;Timer.Elapsed&#8221; -Action $action <br \/>} <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">DoEvery 2 { [Console]::Beep(300, 100) } <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">## Eventually <br \/>Unregister-PsEvent &#8220;Timer.Elapsed&#8221; <\/font><\/p>\n<\/blockquote>\n<p><strong>(Warning: The $args parameter in event actions has changed since CTP2 based on usability feedback. Such is the risk and reward of pre-release software!)<\/strong><\/p>\n<p>However, doing two things at once means multithreading. And multithreading? Thar be dragons! To prevent you from having to deal with multi-threading issues, PowerShell tightly controls the execution of these script blocks. When it&#8217;s time to process an action, it suspends the current script or pipeline, executes the action, and then resumes where it left off. It only processes one action at a time.<\/p>\n<p>One great use of asynchronous actions is engine events, and the Register-PsEvent cmdlet:<\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier New\">## Now in your profile <br \/>$maximumHistoryCount = 1kb <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">## Register for the engine shutdown event <br \/>Register-PsEvent ([System.Management.Automation.PsEngineEvent]::Exiting) -Action { <br \/>&nbsp;&nbsp; Get-History -Count $maximumHistoryCount | ? { $_.CommandLine -ne &#8220;exit&#8221; } | <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Export-CliXml (Join-Path (Split-Path $profile) &#8220;commandHistory.clixml&#8221;) <br \/>} <\/font><\/p>\n<p><font size=\"1\" face=\"Courier New\">## Load our previous history <br \/>$historyFile = (Join-Path (Split-Path $profile) &#8220;commandHistory.clixml&#8221;) <br \/>if(Test-Path $historyFile) <br \/>{ <br \/>&nbsp;&nbsp; Import-CliXml $historyFile | Add-History <br \/>}<\/font><\/p>\n<\/blockquote>\n<h2>Support Events<\/h2>\n<p>Ultimately, almost any event can be written in terms of Register-ObjectEvent. PowerShell is all about task-based abstractions, though, so event forwarding lets you (and ISVs) map complex event domains (such as WMI queries) to much simpler ones. The -SupportEvent parameter to the event registration cmdlets help there, as its event registrations don&#8217;t show in the default views: <\/p>\n<blockquote>\n<p><font size=\"1\" face=\"Courier N\">## Enable process creation events <br \/>function Enable-ProcessCreationEvent <br \/>{ <br \/>&nbsp;&nbsp; $query = New-Object System.Management.WqlEventQuery &#8220;__InstanceCreationEvent&#8221;, ` <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (New-Object TimeSpan 0,0,1), ` <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;TargetInstance isa &#8216;Win32_Process'&#8221; <br \/>&nbsp;&nbsp; $processWatcher = New-Object System.Management.ManagementEventWatcher $query <\/font><\/p>\n<p><font size=\"1\" face=\"Courier N\">&nbsp;&nbsp; $identifier = &#8220;WMI.ProcessCreated&#8221; <br \/>&nbsp;&nbsp; Register-ObjectEvent $processWatcher &#8220;EventArrived&#8221; -SupportEvent $identifier -Action { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [void] (New-PsEvent &#8220;PowerShell.ProcessCreated&#8221; -Sender $args[0] -EventArguments $args[1].SourceEventArgs.NewEvent.TargetInstance) <br \/>&nbsp;&nbsp; } <br \/>} <\/font><\/p>\n<p><font size=\"1\" face=\"Courier N\">## Disable process creation events <br \/>function Disable-ProcessCreationEvent <br \/>{ <br \/>&nbsp;&nbsp; Unregister-PsEvent -Force -SourceIdentifier &#8220;WMI.ProcessCreated&#8221; <br \/>} <\/font><\/p>\n<p><font size=\"1\" face=\"Courier N\">## Register for the custom &#8220;PowerShell.ProcessCreated&#8221; engine event <br \/>Register-PsEvent &#8220;PowerShell.ProcessCreated&#8221; -Action { <br \/>&nbsp;&nbsp; $processName = $args[1].SourceArgs[0].Name.Split(&#8220;.&#8221;)[0] <br \/>&nbsp;&nbsp; (New-Object -COM Sapi.SPVoice).Speak(&#8220;Welcome to $processName&#8221;) <br \/>} <\/font><\/p>\n<p><font size=\"1\" face=\"Courier N\">## Eventually <br \/>Unregister-PsEvent PowerShell.ProcessCreated <\/font><\/p>\n<\/blockquote>\n<p><strong>(Warning: The $args parameter in event actions has changed since CTP2 based on usability feedback. Such is the risk and reward of pre-release software!)<\/strong><\/p>\n<h2>Event Forwarding<\/h2>\n<p>When registering for an event on a remote machine, you can specify the &#8220;-Forward&#8221; parameter if you want to deliver the event to the client session connected to the remote machine. Here&#8217;s an example:<\/p>\n<div>\n<table border=\"0\" cellSpacing=\"0\" cellPadding=\"5\">\n<tbody>\n<tr>\n<td vAlign=\"top\">\n<div>001<br \/>002<br \/>003<br \/>004<br \/>005<br \/>006<br \/>007<br \/>008<br \/>009<br \/>010<br \/>011<br \/>012<br \/>013<br \/>014<br \/>015<br \/>016<br \/>017<br \/>018<br \/>019<br \/>020<br \/>021<br \/>022<br \/>023<br \/>024<br \/>025<br \/>026<br \/>027<br \/>028<br \/>029<br \/>030<br \/>031<br \/>032<br \/>033<br \/>034<br \/>035<br \/>036<br \/>037<br \/>038<br \/>039<br \/>040<br \/>041<br \/>042<\/div>\n<\/td>\n<td vAlign=\"top\" noWrap>\n<div><span>$remoteComputer<\/span><span>&nbsp;<\/span><span>=<\/span><span>&nbsp;<\/span><span>&#8220;REMOTE_COMPUTER&#8221;<\/span><br \/><span>$session<\/span><span>&nbsp;<\/span><span>=<\/span><span>&nbsp;<\/span><span>New-PsSession<\/span><span>&nbsp;<\/span><span>$remoteComputer<\/span><\/p>\n<p><span>Unregister-Event<\/span><span>&nbsp;<\/span><span>WMI.Service.Stopped<\/span><span>&nbsp;<\/span><span>-ErrorAction<\/span><span>&nbsp;<\/span><span>SilentlyContinue<\/span><\/p>\n<p><span>## Register for an event that fires when a service stops on the remote computer<\/span><br \/><span>## Set this to forward to the client machine.<\/span><br \/><span>Invoke-Command<\/span><span>&nbsp;<\/span><span>$session<\/span><span>&nbsp;<\/span><span>{<\/span><\/p>\n<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>## The WMI query to detect a stopping service<\/span><br \/><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>$query<\/span><span>&nbsp;<\/span><span>=<\/span><span>&nbsp;<\/span><span>@&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT *<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FROM __InstanceModificationEvent<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WITHIN 2<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE TargetInstance ISA &#8216;Win32_Service&#8217;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AND TargetInstance.Name = &#8216;WSearch&#8217;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AND TargetInstance.State = &#8216;Stopped&#8217;<br \/>&#8220;@<\/span><\/p>\n<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>## Register for the WMI event<\/span><br \/><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>Register-WmiEvent<\/span><span>&nbsp;<\/span><span>-Query<\/span><span>&nbsp;<\/span><span>$query<\/span><span>&nbsp;<\/span><span>&#8220;WMI.Service.Stopped&#8221;<\/span><span>&nbsp;<\/span><span>-Forward<\/span><br \/><span>}<\/span><\/p>\n<p><span>## Register for notifications from remote computers with the event name<\/span><br \/><span>## WMI.Service.Stopped. We&#8217;ll LOCALLY show the event log of the computer<\/span><br \/><span>## that generated it.<\/span><br \/><span>$null<\/span><span>&nbsp;<\/span><span>=<\/span><span>&nbsp;<\/span><span>Register-EngineEvent<\/span><span>&nbsp;<\/span><span>WMI.Service.Stopped<\/span><span>&nbsp;<\/span>`<br \/><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>-Action<\/span><span>&nbsp;<\/span><span>{<\/span><span>&nbsp;<\/span><span>Show-EventLog<\/span><span>&nbsp;<\/span><span>$event<\/span><span>.<\/span><span>ComputerName<\/span><span>&nbsp;<\/span><span>}<\/span><\/p>\n<p><span>## Stop the service<\/span><br \/><span>Invoke-Command<\/span><span>&nbsp;<\/span><span>$session<\/span><span>&nbsp;<\/span><span>{<\/span><span>&nbsp;<\/span><span>Stop-Service<\/span><span>&nbsp;<\/span><span>WSearch<\/span><span>&nbsp;<\/span><span>}<\/span><\/p>\n<p><span>## Play in the shell<\/span><br \/><span>1<\/span><span>..<\/span><span>10<\/span><span>&nbsp;<\/span><span>|<\/span><span>&nbsp;<\/span><span>%<\/span><span>&nbsp;<\/span><span>{<\/span><span>&nbsp;<\/span><span>Write-Host<\/span><span>&nbsp;<\/span><span>&#8220;Sleeping&#8230;&#8221;<\/span><span>;<\/span><span>&nbsp;<\/span><span>Start-Sleep<\/span><span>&nbsp;<\/span><span>1<\/span><span>&nbsp;<\/span><span>}<\/span><\/p>\n<p><span>Read-Host<\/span><span>&nbsp;<\/span><span>&#8220;Is it fixed?&#8221;<\/span><\/p>\n<p><span>## Fix the service by restarting it.<\/span><br \/><span>Invoke-Command<\/span><span>&nbsp;<\/span><span>$session<\/span><span>&nbsp;<\/span><span>{<\/span><span>&nbsp;<\/span><span>Start-Service<\/span><span>&nbsp;<\/span><span>WSearch<\/span><span>&nbsp;<\/span><span>}<\/span><\/p>\n<p><span>Remove-PsSession<\/span><span>&nbsp;<\/span><span>$session<\/span> <\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Hope this helps getting started.<\/p>\n<p>&#8212; <br \/>Lee Holmes [MSFT] <br \/>Windows PowerShell Development <br \/>Microsoft Corporation<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; The second CTP of PowerShell V2 (CTP2) introduces another new feature called PowerShell Eventing. PowerShell Eventing lets you respond to the asynchronous notifications that many objects support. We didn\u2019t get a chance to fully document these cmdlets in the CTP2, though, so here\u2019s a quick start and primer to help you explore the feature. [&hellip;]<\/p>\n","protected":false},"author":600,"featured_media":13641,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[161],"class_list":["post-5991","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-events"],"acf":[],"blog_post_summary":"<p>&nbsp; The second CTP of PowerShell V2 (CTP2) introduces another new feature called PowerShell Eventing. PowerShell Eventing lets you respond to the asynchronous notifications that many objects support. We didn\u2019t get a chance to fully document these cmdlets in the CTP2, though, so here\u2019s a quick start and primer to help you explore the feature. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/5991","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/users\/600"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/comments?post=5991"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/5991\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media\/13641"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media?parent=5991"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/categories?post=5991"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/tags?post=5991"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}