Much of system administration is reactionary: taking some action when a system service shuts down, when files are created or deleted, when changes are made to the Windows registry, or even on a timed interval. The easiest way to respond to system changes is to simply poll for them. If you're waiting for a file to be created, just check for it every once in a while until it shows up. If you're waiting for a process to start, just keep calling the This approach is passable for some events (such as waiting for a process to come or go), but quickly falls apart when you need to monitor huge portions of the system—such as the entire Registry, or file system. An an alternative to polling for system changes, many technologies support automatic notifications—known as events. When an application registers for these automatic notifications, it can respond to them as soon as they happen—rather than having to poll for them. Unfortunately, each technology offers its own method of event notification. .NET defines one approach, while WMI defines another. When you have a script that wants to generate its own events, neither technology offers an option. PowerShell addresses this complexity by introducing a single, consistent, set of event-related cmdlets. These cmdlets let you work with all of these different event sources. When an event occurs, you can let PowerShell store the notification for you in its event queue, or use an PS > "Hello" > file.txt PS > Get-Item file.txt Directory: C:\temp Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2/21/2010 12:57 PM 16 file.txt PS > Get-Process notepad Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 64 3 1140 6196 63 0.06 3240 notepad PS > Register-WmiEvent Win32_ProcessStopTrace ` >> -SourceIdentifier ProcessStopWatcher ` >> -Action { >> if($EventArgs.NewEvent.ProcessName -eq "notepad.exe") >> { >> Remove-Item c:\temp\file.txt >> } >> } PS > Stop-Process -n notepad PS > Get-Item c:\temp\file.txt Get-Item : Cannot find path 'C:\temp\file.txt' because it does not exist. By building on PowerShell eventing, you can write scripts to quickly react to an ever-changing system. Use the PS > $timer = New-Object Timers.Timer PS > $timer.Interval = 1000 PS > Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed ` >> -Action { $GLOBAL:lastRandom = Get-Random } >> Id Name State HasMoreData Location -- ---- ----- ----------- -------- 2 Timer.Elapsed NotStarted False PS > $timer.Enabled = $true PS > $lastRandom 836077209 PS > $lastRandom 2030675971 PS > $lastRandom 1617766254 PS > Unregister-Event Timer.Elapsed PowerShell's event registration cmdlets give you a consistent way to interact with many different event technologies: .NET events, WMI events, and PowerShell engine events. By default, when you register for an event, PowerShell adds a new entry to the session-wide event repository called the event queue. You can use the In addition to its support for manual processing of events, you can also supply a script block to the However, doing two things at once means multithreading. And multithreading? Thar be dragons! To prevent you from having to deal with multithreading issues, PowerShell tightly controls the execution of these script blocks. When it'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. PS > $timer = New-Object Timers.Timer PS > $timer.Interval = 1000 PS > Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed ` >> -Action { Write-Host "Processing event" } >> $timer.Enabled = $true PS > while($true) { Write-Host "Processing loop"; Sleep 1 } Processing loop Processing event Processing loop Processing event Processing loop Processing event Processing loop Processing event Processing loop (...) Inside of the
In addition to the script block that you supply to the To prevent your script block from accidentally corrupting the state of scripts that it interrupts, PowerShell places it in a very isolated environment. Primarily, PowerShell gives you access to your event action through its job infrastructure. As with other PowerShell jobs, you can use the PS > $timer = New-Object Timers.Timer PS > $timer.Interval = 1000 PS > Register-ObjectEvent $timer Elapsed -SourceIdentifier Timer.Elapsed ` >> -Action { >> $SCRIPT:triggerCount = 1 + $SCRIPT:triggerCount >> "Processing Event $triggerCount" >> } >> $timer.Enabled = $true Id Name State HasMoreData Location -- ---- ----- ----------- -------- 1 Timer.Elapsed NotStarted False PS > Get-Job 1 Id Name State HasMoreData Location -- ---- ----- ----------- -------- 1 Timer.Elapsed Running True PS > Receive-Job 1 Processing Event 1 Processing Event 2 Processing Event 3 (...) For more information about working with PowerShell jobs, see the section called “Invoke a Long-Running or Background Command”. In addition to exposing your event actions through a job interface, PowerShell also uses a Module to ensure that your For more information about useful .NET and WMI events, see Appendix I, Selected Events and Their Uses. You want to create new events for other scripts to consume, or want to respond automatically when they occur. Use the PS > Register-EngineEvent -SourceIdentifier Custom.Event ` >> -Action { Write-Host "Received Event" } >> PS > $null = New-Event Custom.Event Received Event The One prime use of the To accomplish this goal, you use the In this scenario, the events registrations that interact with .NET or WMI directly are merely "support" events, and users would not expect to see them when they use the Here is an example of two functions to easily notify you when a new process starts: ## Enable process creation events function Enable-ProcessCreationEvent { $identifier = "WMI.ProcessCreated" $query = "SELECT * FROM __instancecreationevent " + "WITHIN 5 " + "WHERE targetinstance isa 'win32_process'" Register-WmiEvent -Query $query -SourceIdentifier $identifier ` -SupportEvent -Action { [void] (New-Event "PowerShell.ProcessCreated" ` -Sender $sender -EventArguments $EventArgs.NewEvent.TargetInstance) } } function Disable-ProcessCreationEvent { Unregister-Event -Force -SourceIdentifier "WMI.ProcessCreated" } When used in the shell, the experience is much simpler than working with the WMI events directly: PS > Enable-ProcessCreationEvent PS > calc PS > Get-Event ComputerName : RunspaceId : feeda302-4386-4360-81d9-f5455d74950f EventIdentifier : 2 Sender : System.Management.ManagementEventWatcher SourceEventArgs : SourceArgs : {calc.exe} SourceIdentifier : PowerShell.ProcessCreated TimeGenerated : 2/21/2010 3:15:57 PM MessageData : PS > (Get-Event).SourceArgs (...) Caption : calc.exe CommandLine : "C:\Windows\system32\calc.exe" CreationClassName : Win32_Process CreationDate : 20100221151553.574124-480 CSCreationClassName : Win32_ComputerSystem CSName : LEEHOLMES1C23 Description : calc.exe ExecutablePath : C:\Windows\system32\calc.exe (...) PS > Disable-ProcessCreationEvent PS > notepad PS > Get-Event ComputerName : RunspaceId : feeda302-4386-4360-81d9-f5455d74950f EventIdentifier : 2 Sender : System.Management.ManagementEventWatcher SourceEventArgs : SourceArgs : {calc.exe} SourceIdentifier : PowerShell.ProcessCreated TimeGenerated : 2/21/2010 3:15:57 PM MessageData : In addition to events that you create, engine events also represent events generated by the engine itself. In PowerShell version two, the only defined engine event is PowerShell treats engine events like any other type of event. You can use the You want to automatically perform an action when an event arrives, but automatically remove the event subscription once that event fires. To create an event subscription that automatically removes itself once processed, remove the event subscriber and related job as the final step of the event action. The Example 31.1. Register-TemporaryEvent.ps1 ############################################################################## param($object, $event, [ScriptBlock] $action) Set-StrictMode -Version Latest $actionText = $action.ToString() $actionText += @' $eventSubscriber | Unregister-Event $eventSubscriber.Action | Remove-Job '@ $eventAction = [ScriptBlock]::Create($actionText) $null = Register-ObjectEvent $object $event -Action $eventAction When you provide a script block for the -Action parameter of Fortunately, PowerShell automatically populates several variables for event actions, one of the most important being You have a client connected to a remote machine through PowerShell Remoting, and want to be notified when an event occurs on that machine. Use any of PowerShell's event registration cmdlets to subscribe to the event on the remote machine. Then, use the PS > Get-Event PS > $session = New-PsSession leeholmes1c23 PS > Enter-PsSession $session [leeholmes1c23]: PS C:\> $timer = New-Object Timers.Timer [leeholmes1c23]: PS C:\> $timer.Interval = 1000 [leeholmes1c23]: PS C:\> $timer.AutoReset = $false [leeholmes1c23]: PS C:\> Register-ObjectEvent $timer Elapsed ` >> -SourceIdentifier Timer.Elapsed -Forward [leeholmes1c23]: PS C:\> $timer.Enabled = $true [leeholmes1c23]: PS C:\> Exit-PsSession PS > PS > Get-Event ComputerName : leeholmes1c23 RunspaceId : 053e6232-528a-4626-9b86-c50b8b762440 EventIdentifier : 1 Sender : System.Timers.Timer SourceEventArgs : System.Management.Automation.ForwardedEventArgs SourceArgs : {System.Timers.Timer, System.Timers.ElapsedEventArgs} SourceIdentifier : Timer.Elapsed TimeGenerated : 2/21/2010 11:01:54 PM MessageData : PowerShell's eventing infrastructure lets you define one of three possible actions when you register for an event:
The For more information about registering for events, see the section called “Respond to Automatically-Generated Events”. For more information about PowerShell Remoting, see Chapter 29, Remoting. Retrieve the event subscriber, and then interact with the PS > $null = Register-EngineEvent -SourceIdentifier Custom.Event ` >> -Action { >> "Hello World" >> >> Write-Error "Got an Error" >> >> $SCRIPT:privateVariable = 10 >> } >> PS > $null = New-Event Custom.Event PS > $subscriber = Get-EventSubscriber Custom.Event PS > $subscriber.Action | Format-List Module : __DynamicModule_f2b39042-e89a-49b1-b460-6211b9895acc StatusMessage : HasMoreData : True Location : Command : "Hello World" Write-Error "Got an Error" $SCRIPT:privateVariable = 10 JobStateInfo : Running Finished : System.Threading.ManualResetEvent InstanceId : b3fcceae-d878-4c8b-a53e-01873f2cfbea Id : 1 Name : Custom.Event ChildJobs : {} Output : {Hello World} Error : {Got an Error} Progress : {} Verbose : {} Debug : {} Warning : {} State : Running PS > $subscriber.Action.Error Write-Error : Got an Error At line:4 char:20 + Write-Error <<<< "Got an Error" + CategoryInfo : NotSpecified: (:) [Write-Error], WriteError Exception + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorExc eption,Microsoft.PowerShell.Commands.WriteErrorCommand When you supply an For more information about working with PowerShell jobs, see the section called “Invoke a Long-Running or Background Command”. In addition to the job interface, PowerShell's event system generates a module to isolate your script block from the rest of the system—for the benefit of both you and the system. When you want to investigate the internal state of your action, PowerShell surfaces this state through the action's PS > $module = $subscriber.Action.Module PS > & $module { dir variable:\privateVariable } Name Value ---- ----- privateVariable 10 To make this even easier, you can use the For objects that support a .NET delegate, simply assign the script block to that delegate: $replacer = { param($match) $chars = $match.Groups[0].Value.ToCharArray() [Array]::Reverse($chars) $chars -join '' } PS > $regex = [Regex] "\w+" PS > $regex.Replace("Hello World", $replacer) olleH dlroW To have a script block directly handle a .NET event, call that object's $form.Add_Shown( { $form.Activate(); $textbox.Focus() } ) When working with some .NET developer APIs, you might run into a method that takes a delegate as one of its arguments. Delegates in .NET act as a way to provide custom logic to a .NET method that accepts them. For example, the solution supplies a custom delegate to the regular expression As another example, many array classes support custom delegates for searching, sorting, filtering, and more. In this example, we create a custom sorter to sort an array by the length of its elements: PS > $list = New-Object System.Collections.Generic.List[String] PS > $list.Add("1") PS > $list.Add("22") PS > $list.Add("3333") PS > $list.Add("444") PS > $list.Add("5") PS > $list.Sort( { $args[0].Length - $args[1].Length } ) PS > $list 5 1 22 444 3333 Perhaps the most useful delegete per character is the ability to customize the behavior of the .NET Framework when it encounters an invalid certificate in a web network connection. This happens, for example, when you try to connect to a website that has an expired SSL certificate. The .NET Framework lets you override this behavior through a delegate that you supply to the [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } In addition to delegates, you can also assign PowerShell script blocks directly to events on .NET objects. Normally, you'll want to use PowerShell eventing to support this scenario. PowerShell eventing provides a very rich set of cmdlets that let you interact with events from many technologies: .NET, WMI, and the PowerShell engine itself. When you use PowerShell eventing to handle .NET events, PowerShell protects you from the dangers of having multiple script blocks running at once, and from them interfering with the rest of your PowerShell session. However, when you write a self-contained script that uses events to handle events in a WinForms application, directly assigning script blocks to those events can be a much lighter-weight development experience. To see an example of this approach, see the section called “Program: Add a Graphical User Interface to Your Script”. For more information about PowerShell's event handling, see the section called “Respond to Automatically-Generated Events”. Copyright © 2010 All trademarks and registered trademarks appearing on labs.oreilly.com are the property of their respective owners. |
posh | Powershell > posh | notes >