PowerShell Events with .NET (Events: Part 1)

PowerShell events are one of those “dark” corners within the PowerShell universe. Aside from the help associated with the individual cmdlets involved with events, there is little information within PowerShell. Most broad functional aspects of PowerShell get an “about_” page or two, eventing does not. The best guide to PowerShell eventing I have found is chapter 20 of Bruce Payette’s Windows PowerShell In Action. It is the definative resource on all things PowerShell, and definitely eventing. Regardless of your interest in eventing it is worth the investment if you heading into intermediate or advanced use of PowerShell. Luckily Microsoft’s The Scripting Guy site did a two part-er on PowerShell events with Bruce Payette that includes an abridged version of that chapter, part one is here and part two is here.

There are actually 3 types of events you can consume in PowerShell and they use pretty much the same set of cmdlets. The 3 types are .NET object events, WMI events, and Engine events. They are mostly self explanatory, .NET object events allow you to subscribe to events generated by .NET objects, WMI events allow you to subscribe to the many Windows Management Instrumentation events, and Engine events allow you to subscribe to events generated by the Windows PowerShell engine itself. I’m familiar with .NET events and WMI events and have been playing with those recently but PowerShell engine events sound very intriguing and as of yet I have not experimented with those. I’ll be posting more as I experient with events in PowerShell more and more, but today I’ve choosen .NET events as a means of introducing eventing in PowerShell.

Typically when you script you compose a set of statements that are executed as the flow of your script dictates. Events are a little different. Your set of statements are still executed though only when a specific event or action occurs. Those statements could be executed never, once, or as many times as the event happens and for as long as you the developer deems necessary to complete your task. Bruce Payette in his book describes it like this, “Don’t call me, I’ll call you”.

Today I will break down a simple script I wrote to test out an event that is available in .NET known as a FileSystemWatcher. This event does pretty much what it says, it watches the file system. When it is setup to watch the file system it waits until a certain activity (or event) occurs and then fires a notification that this event happened. In FileWatcher’s case it can be tasked to watch for changed, created, deleted, or renamed events. There are a couple others but these are the usual. When these notifications are fired we can setup PowerShell, using the various eventing cmdlets, to act (by executing PowerShell statements). This is essentially the event “calling you”.

FileWatcher_Events.ps1

I’ll breakdown my test script below.

$watcher = New-Object System.IO.FileSystemWatcher -Property @{Path = 'D:\PowerShell\Temp';
                                                              Filter = '*.txt';
                                                              NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'}

Here I create an instance of the .NET System.IO.FileSystemWatcher object. I also specify several properties that I'm interested in at the same time. If you want to see all the properties, methods, and events associated with a FileSystemWatcher object go ahead and create your own instance as above and simply use Get-Member in your PowerShell console on that variable. Remeber Get-Member is your friend. If you did that you can see that those properties I specified above are available and can be set as necessary. The Path property is the directory that you want this instance of FileSystemWatcher to watch. The Filter property is the file type and or name portion you want to watch in that directory, you could also say look for files with 'log' in part of the name with Filter = '*log*'. The NotifyFilter are the changes you want to watch for, in this case this instance will watch for changes to FileName and LastWrite. You have to cast the string on NotifyFilter to the System.IO.NotifyFilters type. There are a number of available types for most of the things you would expect to be changed on a file system. Search for the System.IO.NotifyFilters and you will find a listing with descriptions.

$CreatedAction =
{
  $ParentPath = Split-Path -Path $event.sourceEventArgs.FullPath
  $NewDestPath = Join-Path -Path $ParentPath -ChildPath $(($event.sourceEventArgs.Name).Split('.')[0])
  New-Item -Path $NewDestPath -Type Directory
  Start-Sleep -Seconds 2
  Move-Item -Path $($event.sourceEventArgs.FullPath) -Destination $NewDestPath
}            

$DeleteAction =
{
  "File Deleted: $($event.sourceEventArgs.FullPath)" | Out-File -FilePath D:\PowerShell\DeleteActionLog.txt -Append
}

The next statements are just a couple of script blocks that I save to a variable for later use. These are basically the PowerShell code that will be executed when the events fire. These could be as simple or as complex as you need. Basically any valid PowerShell can be used here. The $CreatedAction script block creates a directory based on the name of the file being created, it then sleeps a few seconds (this is just really there so you can see whats going on). Finally it moves the file that was created into the directory that was created. Of note here is the $event variable. This is a PowerShell automatic variable that is available within PowerShell eventing and is maintained by PowerShell. It is similar to the $_ in a ForEach or on the pipeline, containing the object of the moment. In this case $event contains the fired event details.

Register-ObjectEvent -InputObject $watcher -EventName Created -SourceIdentifier FileCreated -Action $CreatedAction
Register-ObjectEvent -InputObject $watcher -EventName Deleted -SourceIdentifier FileDeleted -Action $DeleteAction

This last bit is where all the PowerShell event magic happens. In PowerShell you subscribe to events using the Register-ObjectEvent cmdlet. When these subscriptions are established they are notified of events as they occur and then execute the specified action. There are specific register cmdlets for each of the PowerShell event types (.NET, WMI, and Engine). Register-ObjectEvent is what we are using here to subscribe to the .NET FileSystemWatcher events.

The code above creates two subscriptions with the $watcher instance created above. One for creation events where the -EventName property is 'Created'. The second for deletion events where the property is 'Deleted'. These properties correspond to the event names as you may have seen by doing Get-Member above. The -SourceIdentifier property is for granting a meaningful name for the subscription. This property is useful with other eventing cmdlets so it is helpful to use something useful. The -Action property is a script block that will be executed when the subscription is advised that a corresponding event has occurred.

If you dot source this script into your PowerShell console (. .\FileWatcher_Events.ps1) you should be able to see it in action. Be sure to change the file path and log file path in the $watcher instance and $DeleteAction to something relevant for your environment. Once sourced you can use the Get-EventSubscriber cmdlet to see the subscriptions for 'FileCreated' and 'FileDeleted'. You can test the event subscriptions with the following PowerShell code

"Hello?" | Out-File D:\PowerShell\Temp\Calling.txt

This will almost immeadeately create a directory named Calling. Two seconds later you should see the Calling.txt file moved into the Calling directory. The 'FileCreated' action contains a Move-Item statement which performs a copy and a delete. As such you should see the DeleteActionLog.txt file now contain a line "File Deleted: D:\PowerShell\Temp\Calling.txt.

You can also unregister the two subscriptions using the Unregister-Event cmdlet. Although there are seperate cmdlets for creation of subscription the other cmdlets work the same for all types of subscriptions. You can unregister them one at a time or you can unregister en masse using the pipeline.

Unregister-Event -SourceIdentifier FileCreated
Get-EventSubscriber | Unregister-Event

PowerShell eventing is a powerful aspect of PowerShell. It is one of the aspects of PowerShell that I have been trying to gain a better understanding of. In combination with the deployment strategy that I have been working with, and wrote about earlier, eventing can be very efficient, timely, and useful. Let me know in the comments what resources you've discovered for learning and deploying PowerShell eventing?

About these ads

2 responses to “PowerShell Events with .NET (Events: Part 1)

  1. Hi

    It is a very good article. I was trying invoke a powershell script whenever there is a change in a folder. I have modified your createdaction as follows.

    $CreatedAction =
    {
    $FilenamewithoutExtn=$(($event.sourceEventArgs.Name).Split(‘.’)[0])
    Start-Job $ScriptLoc\$FilenamewithoutExtn.ps1
    Get-Job -State Running | Wait-Job
    }

    But it doesn’t work as intended

    My script file will just display a message. ([system.windows.forms.messagebox]::show(“Hello, Test1!”)).

    Can you please guide that where am i missing?

    • I’ve only just started to scratch the surface of eventing under PowerShell, and my knowledge is still superficial at best. However I believe that the scriptblock that is specified for the -Action parameter in the event registration spawns a new Runspace. What I’m not real clear on is how Runspaces, Sessions, and scope across all of that really shakeout.

      Given that I do see 2 possible issues with your code snippet. The $ScriptLoc variable may not be scoped in such a way that the value you specify elsewhere in your code is visible to the scriptblock that makes up your action. You might want to look at help about_scopes for more information there. You could try, at least for validating, using a real path i.e. C:\MyScriptsFolder\$FilenamewithoutExtn.ps1.

      The other potential issue is the Start-Job command creates a new Session, from your new Runspace, created from the event action scriptblock. Without seeing more of your code or doing more testing I’m not sure what the experience there would be. However I do think using the messagebox static method like you are in your Start-Job, as well as within the Action script block, would not display anywhere. This is because it would be in a session that would not be interactive with your desktop. You might want to do some basic testing with Write-Host or Out-File to test how your code is working in this situation.

      Hope that helps put you in the right direction. Thanks for stopping by.

      Joel.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s