Search code examples
powershellfilesystemwatcher

Powershell Change Watcher only Triggers on directories


I am building a file watching script in powershell. I have been manipulating code from: https://gallery.technet.microsoft.com/scriptcenter/Powershell-FileSystemWatche-dfd7084b

Everything seemed to work fine, but when I did some actual testing I noticed the change event was only triggering on the parent directory and not the actual file itself. Below is the code I have poached and updated:

$folder = 'C:\extended_attributes\testing\DesignerVistaReorganize' # Enter the root path you want to monitor. 
$filter = '*.*'  # You can enter a wildcard filter here. 

# In the following line, you can change 'IncludeSubdirectories to $true if required.                           
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'} 

# Here, all three events are registerd.  You need only subscribe to events that you need: 

Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { 
    $name        = $Event.SourceEventArgs.Name 
    $changeType  = $Event.SourceEventArgs.ChangeType 
    $timeStamp   = $Event.TimeGenerated 
    $filePath    = -join($folder,"\",$name) | Out-String
    Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore green 
    Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"} 

Register-ObjectEvent $fsw Deleted -SourceIdentifier FileDeleted -Action { 
    $name        = $Event.SourceEventArgs.Name 
    $changeType  = $Event.SourceEventArgs.ChangeType 
    $timeStamp   = $Event.TimeGenerated 
    $filePath    = -join($folder,"\",$name) | Out-String
    Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore red 
    Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"} 

Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action { 
    $name        = $Event.SourceEventArgs.Name 
    $changeType  = $Event.SourceEventArgs.ChangeType 
    $timeStamp   = $Event.TimeGenerated 
    $filePath    = -join($folder,"\",$name) | Out-String
    Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore white 
    Out-File -FilePath c:\scripts\filechange\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"} 

The delete and create events are working as one would expect (as shown below):

File Creation:

The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Cart_Warnings\newFile.txt' Created at 10/01/2018 14:55:35

File Change:

The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Cart_Warnings'

was Changed at 10/01/2018 15:01:18

File Deletion:

The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Cart_Warnings\newFile.txt' Deleted at 10/01/2018 14:56:47

Another thing I noticed is that the change event is only firing on the first file change under the root directory. The other two events fire whenever a file is created or deleted. Has anyone run into this issue before and if so can you please provide some insight on a work around?

Work Around (not a valid answer though)

I have tried a bunch of differnt things trying to make this event fire consistently, and only one seems to work. I have two scripts for file change events. They are pretty much identical with the exception that they call each other after the event has completed. The key is not ensure that you kill the watcher by unregistering the event before the second script is called.

Script 1

$folder = 'C:\extended_attributes\testing\DesignerVistaReorganize' # Enter the root path you want to monitor. 
$filter = '*.*'  # You can enter a wildcard filter here. 

# In the following line, you can change 'IncludeSubdirectories to $true if required.                           
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'} 

Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action { 
        echo "Script 1"
        $name = $Event.SourceEventArgs.Name 
        $changeType = $Event.SourceEventArgs.ChangeType 
        $timeStamp = $Event.TimeGenerated 
        $filePath    = -join($folder,"\",$name) | Out-String
        Unregister-Event FileChanged
        Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore white 
        Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"
        .\change_watcher_2.ps1
} 

Script 2

$folder = 'C:\extended_attributes\testing\DesignerVistaReorganize' # Enter the root path you want to monitor. 
$filter = '*.*'  # You can enter a wildcard filter here. 

# In the following line, you can change 'IncludeSubdirectories to $true if required.                           
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'} 


Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action { 
        echo "Script 2"
        $name = $Event.SourceEventArgs.Name 
        $changeType = $Event.SourceEventArgs.ChangeType 
        $timeStamp = $Event.TimeGenerated 
        $filePath    = -join($folder,"\",$name) | Out-String
        Unregister-Event FileChanged
        Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore white 
        Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"
        .\change_watcher_1.ps1
} 

output

PS C:\extended_attributes\testing> C:\extended_attributes\testing\change_watcher_1.ps1

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
2      FileChanged                     NotStarted    False                                 ...                     



PS C:\extended_attributes\testing> The file 'C:\extended_attributes\testing\DesignerVistaReorganize\BusinessPrinting
' was Changed at 10/02/2018 10:46:43
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Editor_Warnings
' was Changed at 10/02/2018 10:47:09
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile\Mobile_Control_Panel_2015_05_14.dvf
' was Changed at 10/02/2018 10:49:50
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile
' was Changed at 10/02/2018 10:49:58
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile\Gradients_Panels_Colors_Menus_2015_03_01.dvf
' was Changed at 10/02/2018 10:51:49
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile\Tablet_Editor_2015_03_31.dvf
' was Changed at 10/02/2018 10:52:46
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile
' was Changed at 10/02/2018 10:52:57

I would really like a better solution than this as I can foresee many problems arising from this approach.


Solution

  • I found a much better solution than I had posted in the update of my original questions. If you use the attribute to EnableRaisingEvents as shown below. The event will fire everytime a change has been made to the file system:

    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = 'C:\extended_attributes\testing\DesignerVistaReorganize'
    $watcher.IncludeSubdirectories = $true
    $watcher.EnableRaisingEvents = $true
    
    
    $changed = Register-ObjectEvent $watcher "Changed" -Action {
        write-host "Changed: $($eventArgs.FullPath)"
        $creation_time = gi -Path $eventArgs.FullPath    | get-itemproperty | select -ExpandProperty CreationTime | Get-date -Format s
        $file_extension = gi -Path $eventArgs.FullPath   | get-itemproperty | select -ExpandProperty Extension    
        $file_base_name = gi -Path $eventArgs.FullPath   | get-itemproperty | select -ExpandProperty BaseName     
        $last_access_time = gi -Path $eventArgs.FullPath | get-itemproperty | select -ExpandProperty LastAccessTime | Get-date -Format s
        $last_write_time = gi -Path $eventArgs.FullPath  | get-itemproperty | select -ExpandProperty LastWriteTime | Get-date -Format s
    
        Write-Host "Creation time: $($creation_time)"
        Write-Host "ex: $($file_extension)"
        Write-Host "Name: $($file_base_name)"
        Write-Host "access: $($last_access_time)"
        Write-Host "write: $($last_write_time)"
    
        $object = New-Object System.Object
        $object | Add-Member -type NoteProperty –Name Extension –Value  $file_extension
        $object | Add-Member -type NoteProperty –Name BaseName –Value $file_base_name
        $object | Add-Member -type NoteProperty –Name Createtime –Value $creation_time
        $object | Add-Member -type NoteProperty –Name Accesstime –Value $last_write_time
        $object | Add-Member -type NoteProperty –Name Writetime –Value $last_access_time
    
        Write-Host "After Object"
    
        # Load the module
        Import-Module Mdbc
        # Connect the new collection test.test
        Connect-Mdbc . test test -NewCollection
        # add the objects to the collection
        $object | Add-MdbcData
        # Get all data as custom objects and show them in a table
        Get-MdbcData -As PS | Format-Table -AutoSize | Out-String
    }