Using FileSystemWatcher Correctly (Part 1)

A FileSystemWatcher can monitor a file or folder for changes, so your PowerShell code can immediately be notified when new files are copied to a folder, or when files are deleted or changed.

Often, you find example code for synchronous monitoring like this:

# make sure you adjust this
$PathToMonitor = "c:test"


$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path  = $PathToMonitor
$FileSystemWatcher.IncludeSubdirectories = $true

Write-Host "Monitoring content of $PathToMonitor"
explorer $PathToMonitor
while ($true) {
  $Change = $FileSystemWatcher.WaitForChanged('All', 1000)
  if ($Change.TimedOut -eq $false)
  {
      # get information about the changes detected
      Write-Host "Change detected:"
      $Change | Out-Default

      # uncomment this to see the issue
      #Start-Sleep -Seconds 5
  }
  else
  {
      Write-Host "." -NoNewline
  }
}

This example works just fine. When you add files to the folder being monitored, or make changes, the type of change is detected. You could easily take that information and invoke actions. For example, in your IT, people could drop files with instructions into a drop folder, and your script could respond to these files.

However, this approach has a back side: when a change is detected, control is returned to your script so it can process the change. If another file change occurs while your script is no longer waiting for events, it gets lost. You can easily check for yourself:

Add a lengthy statement such as “Start-Sleep -Seconds 5” to the code that executes when a change was detected, then apply multiple changes to your folder. As you’ll see, the first change will be detected, but during the five second interval in which PowerShell is kept busy, all other change events get lost.

In tomorrows’ tip we’ll make sure your FileSystemWatcher isn’t skipping any changes!

Twitter This Tip! ReTweet this Tip!

Responding to New Event Log Entries (Part 2)

Here is another file system task that sounds worse than it actually is. Let’s say you need to remove all folders below a given level in a folder structure. Here is how:

# set the event log name you want to subscribe to
# (use Get-EventLog -AsString for a list of available event log names)
$Name = 'Application'

# get an instance
$Log = [System.Diagnostics.EventLog]$Name

# determine what to do when an event occurs
$Action = {
    # get the original event entry that triggered the event
    $entry = $event.SourceEventArgs.Entry

    # log all events
    Write-Host "Received from $($entry.Source): $($entry.Message)"

    # do something based on a specific event
    if ($entry.EventId -eq 1 -and $entry.Source -eq 'WinLogon') 
    {
        Write-Host "Test event was received!" -ForegroundColor Red
    }

}

# subscribe to its "EntryWritten" event
$job = Register-ObjectEvent -InputObject $log -EventName EntryWritten -SourceIdentifier 'NewEventHandler' -Action $Action 

# now whenever an event is written to the log, $Action is executed
# use a loop to keep PowerShell busy. You can abort via CTRL+C

Write-Host "Listening to events" -NoNewline

try
{
    do
    {
        Wait-Event -SourceIdentifier NewEventHandler -Timeout 1
        Write-Host "." -NoNewline

    } while ($true)
}
finally
{
    # this executes when CTRL+C is pressed
    Unregister-Event -SourceIdentifier NewEventHandler
    Remove-Job -Name NewEventHandler
    Write-Host ""
    Write-Host "Event handler stopped."
}

While the event handler is active, PowerShell outputs “dots” every second, indicating it is listening. Now open a second PowerShell window, and run this:

Write-EventLog -LogName Application -Source WinLogon -EntryType Information -Message test -EventId 1 

Whenever a new Application event log entry is written, the event handler echos the event details. If the event has an EventID equals 1 and a source of “WinLogon”, like in our test event entry, a red message is output as well.

To end the event handler, press CTRL+C. The code automatically cleans up and removes the event handler from memory.

This all works by using Wait-Event: this cmdlet can wait for a specific event to occur, and while it waits, PowerShell continues to process the event handler. When you specify a timeout (in seconds), the cmdlet returns control to your script. In our example, control is returned every second, enabling the script to output a progress indicator like the dots.

If the user presses CTRL+C, the script won’t stop immediately. Instead, it first executes the finally block and makes sure the event handler is cleaned up and removed.

Twitter This Tip! ReTweet this Tip!

Responding to New Event Log Entries (Part 1)

If you’d like to respond to new event log entries in real time, here is how your PowerShell code can be notified the moment a new event entry is written:

# set the event log name you want to subscribe to
# (use Get-EventLog -AsString for a list of available event log names)
$Name = 'Application'

# get an instance
$Log = [System.Diagnostics.EventLog]$Name

# determine what to do when an event occurs
$Action = {
    # get the original event entry that triggered the event
    $entry = $event.SourceEventArgs.Entry

    # do something based on the event
    if ($entry.EventId -eq 1 -and $entry.Source -eq 'WinLogon') 
    {
        Write-Host "Test event was received!"
    }

}

# subscribe to its "EntryWritten" event
$job = Register-ObjectEvent -InputObject $log -EventName EntryWritten -SourceIdentifier 'NewEventHandler' -Action $Action

This code snippet installs a background event listener which responds whenever the event log emits a “EntryWritten” event. When that occurs, the code in $Action executes. It gets the event that triggered the action by querying the $event variable, and in our example, when the EventId equals 1, and the event source is “WinLogon”, a message is written. Of course, you could as well send off an email, write a log, or do whatever else is useful.

To see the event handler in action, simply write a test event entry that meets the criteria:

# write a fake test event to trigger
Write-EventLog -LogName Application -Source WinLogon -EntryType Information -Message test -EventId 1

Once you run this line, the event handler executes and writes its message to the console.

Note that this example installs an asynchronous handler that works in the background whenever PowerShell is not busy, and for as long as PowerShell runs. You can’t keep the script busy by running Start-Sleep or a loop (because then PowerShell would be busy, and unable to process the event handler in the background). To keep this event handler responsive, you could start the script with the -noexit parameter:

Powershell.exe -noprofile -noexit -file “c:yourpath.ps1”

To remove the event handler, run this:

 
PS> Unregister-Event -SourceIdentifier NewEventHandler 
PS> Remove-Job -Name NewEventHandler 
 

Twitter This Tip! ReTweet this Tip!

Accessing Event Logs Directly

With Get-EventLog, you can easily dump the content for any given event log, however if you’d like to directly access a given event log, you can only use the -List parameter to dump them all, then pick the one you are after:

$SystemLog = Get-EventLog -List | Where-Object { $_.Log -eq 'System' }
$SystemLog

A more direct way uses casting, like this:

$systemLogDirect = [System.Diagnostics.EventLog]'System'
$systemLogDirect

Simply “convert” the event log name into an object of “EventLog” type. The result looks similar to this and provides information about the number of entries and the log file size:

 
PS> $systemLogDirect

  Max(K) Retain OverflowAction        Entries Log                                                       
  ------ ------ --------------        ------- ---                                                       
  20.480      0 OverwriteAsNeeded      19.806 System   
 

Twitter This Tip! ReTweet this Tip!