Header Ads

Header ADS

PS> Use the FileSystemWatcher class to monitor a folder instead of looping.

Background

At some point or another, an administrator will ask themselves, "I just need some way to monitor changes to a specific folder, then move/copy/delete/open/print that file when it shows up." After a little bit of digging, more often than not, said administrator will build some kind of script that will check the status of a file/folder every few seconds.

An alternative to that approach would be to use the FileSystemWatcher .NET class. The FileSystemWatcher class listens to the filesystem change notifications and raises events when a directory, or file in a directory, changes. It is provided by the System.IO namespace.

Benefits of the FileSystemWatcher class

Benefits:
- Use fewer resources -- it no longer needs a constant Do-While or For loops.
- No more "sleep" duration settings to constantly tweak.
- Fewer compatibility risks because it's been available since .NET 1.0.
- Fewer tweaks = easier scale.
- If anything, it's still a fun learning experience to the uninitiated.

Cons:
- A little bit more complex to troubleshoot.

Script

The following script is a basic one that watches a network shared folder for the creation of any Word template files (*.dot) and moves them to a subfolder.
# -----[Declarations]-----
$FSWatcher= New-Object System.IO.FileSystemWatcher
$FSWatcher.IncludeSubdirectories = $false
$FSWatcher.Path = "C:\HoldMyBeer\WatchThis"
# Alternate example "\\SERVER1\HIDDENSHARE$\$env:Username\Documents\WatchThisFolder"
$FSWatcher.Filter = "*.dot"
$FSWatcher.NotifyFilter = [System.IO.NotifyFilters]'FileName, LastWrite'
$FSWatcher.EnableRaisingEvents = $true

# DEBUG # $FSWatcher | Get-Member -MemberType Properties,Event

$ScanDir = $FSWatcher.Path
$ScanLogDir = "\\\\$env:Username\FolderToWatch\"

# Optional: Create a Log file
$WatchLog = Join-Path -Path $ScanLogDir `
	-ChildPath "$($MyInvocation.MyCommand.Name)-$(Get-Date -Format 'MM-dd-yyyy').log"
    
# Define a ScriptBlock action
$PrintPdf = 
{
  $PdfToPrint = $event.SourceEventArgs.FullPath
  $ChangeType = $event.SourceEventArgs.ChangeType
  
  Write-Host "Locked $PdfToPrint was $ChangeType at $(Get-Date)"
  While ($True)
  {
  Try {
      [IO.File]::OpenWrite($PdfToPrint).Close() 
      Break
     }
  Catch { Start-Sleep -Milliseconds 200}
  }
  Write-Host "Available $PdfToPrint was $ChangeType at $(Get-Date)"

  # -----[Print the PDF file]-----
  # Check in the Sumatra GUI that it uses the same printer name as in the 'Select Printer' section 
  $printername = ((Get-WmiObject -Class Win32_Printer).where({$PSItem.Default -eq $true})).Name

  # Cleanup old Log files - Change AddDays value as required 
  Get-ChildItem "$scanlogdir\*.log" | Where LastWriteTime -LT (Get-Date).AddDays(-15) | Remove-Item -Confirm:$false

  # Log event and action 
  "$(get-date) - Printing file - $pdftoprint on Printer - $printername" | Out-File -Append $scanlogname 
  
  # Print
  Start-Process -Wait -FilePath $sumatra -ArgumentList "$pdftoprint -print-to ""$printername"""
    
  Start-Sleep -Seconds 5 
  # Increase Sleep if slow to print otherwise PDF will be removed before it has time to print
  
  # Move or Remove PDF - Will overwrite if same name exists in the backup directory 
  "$(get-date) - Moving file - $pdftoprint to - $scanbackdir directory" | Out-File -Append $scanlogname 
  Remove-Item -force $pdftoprint | Out-File -Append $scanlogname
}

# Register the Event to listen for
Register-ObjectEvent $FSWatcher 'Created' -Action $PrintPdf

# Reference only: Manually unregister the FileSystemWatcher event
# Get-EventSubscriber | Unregister-Event


How it works

In a nutshell, the script will register script-block ($PrintPDF) to the events generated by the .NET object, which, in this case, is the the FileSystemWatcher object ($FSWatcher). 

The actions ($FSWatcher.NotifyFilter) define what actions will raise an event that will trigger the execution of the script-block.

Optionally, you can run the Get-EventSubscriber to list the registrations within your current scope, and the Unregister-Event to kill those registrations as you are debugging your versions of the script.

How to run it

There are several ways to initiate the script. It's important that, by design, the subscription to the FileSystemWatcher events are destroyed when the Powershell script is terminated. This way, subscriptions do not accumulate on a given machine, which may lead to inefficient resource usage and system instability high CPU usage and memory leaks.

Command:
powershell.exe -noexit -noprofile -windowstyle hidden -file "(folder)\Move-DotFiles.ps1"

As always, please note the ExecutionPolicy before running the command.

Relevant Links

Links to the class.

No comments

Powered by Blogger.