5

I need to monitor a folder and perform some action each time a file is created. I have 2 solutions - one using WMI, where I can use this filter (called from either a .MOF file or a Powershell script which registers permanent MWI event bindings) to poll the folder every second :

SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA "Cim_DirectoryContainsFile" AND TargetInstance.GroupComponent="Win32_Directory.Name='C:\\test'"

Example Script :

$query = @"
SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA "Cim_DirectoryContainsFile" AND TargetInstance.GroupComponent="Win32_Directory.Name='C:\\test'"
"@

#Set up hash table for splatting
$wmiParams = @{
    Computername = $env:COMPUTERNAME
    ErrorAction = 'Stop'
    NameSpace = 'root\subscription'
}

######################################################################################################################### Filter
#Creating a new event filter
$wmiParams.Class = '__EventFilter'
$wmiParams.Arguments = @{
    Name = 'WatchFiles'
    EventNamespace = 'root\CIMV2'
    QueryLanguage = 'WQL'
    Query = $query 
}
$filterResult = Set-WmiInstance @wmiParams

######################################################################################################################### Consumer
$wmiParams.Class = 'ActiveScriptEventConsumer'
$wmiParams.Arguments = @{
    KillTimeout = 0
    MachineName = $env:COMPUTERNAME
    ScriptingEngine = 'VBScript'
    ScriptText = 
@"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("c:\test\Log.log", 8, True)
objFile.WriteLine "hellohellohellohellohellohello"
objFile.Close
"@
    ScriptFileName = $null
    Name = 'ActiveScriptEventConsumer'
}
$consumerResult = Set-WmiInstance @wmiParams

######################################################################################################################### Binding
$wmiParams.Class = '__FilterToConsumerBinding'
$wmiParams.Arguments = @{
    Filter = $filterResult
    Consumer = $consumerResult
}
$bindingResult = Set-WmiInstance @wmiParams

Example MOF File :

#PRAGMA AUTOREOVER
#pragma namespace("\\\\.\\root\\subscription")

instance of __EventFilter as $EventFilter
{
    Name  = "Event Filter Instance Name";
    EventNamespace = "Root\\Cimv2";
    Query = "Select * From __InstanceCreationEvent Within 1 "
            "Where TargetInstance Isa \"Cim_DirectoryContainsFile\" "
            "and TargetInstance.GroupComponent=\"Win32_Directory.Name=\'C:\\\\test\'\"";
    QueryLanguage = "WQL";
};

instance of ActiveScriptEventConsumer as $Consumer
{
    Name = "TestConsumer";
    ScriptingEngine = "VBScript";

    ScriptFileName = "C:\\test\\test.vbs"


};

instance of __FilterToConsumerBinding
{
    Filter = $EventFilter;
    Consumer = $Consumer;
}; 

This seems like a good way of doing it, as I can set the interval myself, and working with WMI always feels safe to me as it is essentialy a built in solution in Windows.

Another way of doing this would be to write a script, something like :

$folderpath = 'C:\test'
$items = Get-ChildItem $folderpath
$currentdatetime = Get-Date

foreach($item in $items) {
    If ($item.CreationTime > $currentdatetime.AddSeconds(-5)){
        # Do some action
    }
}

Which can then be run on the system as a scheduled task.

My question is - what is the best way of doing this? Of the 2 options I have shown, is one of them inherently more efficient in terms of system resources or potential for errors?

Are there any other ways of doing this which I have not considered?


Jisaak added an answer which uses System.IO.FilesystemWatcher. Unfortunately this is not ideal for my purposes, as this only works while the shell which executed it is open, whereas I would like a more permanent solution (updated the title of the question to reflect this)

ᄂ ᄀ
  • 5,669
  • 6
  • 43
  • 57
Bassie
  • 9,529
  • 8
  • 68
  • 159

2 Answers2

4

You don't want to long poll a folder for file changes, you should rather use a mechanism where you get notified when a file was created.

Therefore you can use the FileSystemWatcher class and register to the created event handler using the Register-ObjectEvent cmdlet.

$folder = 'C:\test'
$filter = '*.*'
$fileSystemWatcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{
 IncludeSubdirectories = $true # Set this according to your requirements.
 NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}

$onCreated = Register-ObjectEvent $fileSystemWatcher Created -SourceIdentifier FileCreated -Action {
 $path = $Event.SourceEventArgs.FullPath
  Write-Host "File $Path was created".

}

Run this to unregister:

Unregister-Event -SourceIdentifier FileCreated
Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
  • 1
    HI Jisaak thanks for your answer. Why do you think using `FileSystemWatcher` is a better solution? This would also require a Scheduled Task to be set up, so doesn't this mean that there is more maintenance? What are the benefits of doing it that way, and what is wrong with long polling a folder? Surely I could just include in my action script for my permanent event registration to send an email or notify myself in some way without the need for `FileSystemWatcher` Thanks again for the help! – Bassie Apr 27 '16 at 11:33
  • 1
    Well, I didn't research anything on performance but I think that this is the most efficient way to track changes on a directory. Long polling is like always the worst you can do. – Martin Brandl Apr 27 '16 at 11:38
  • Thanks jisaak. I should have mentioned that I have already used a similar method where I register for an event in the shell. Unfortunately this is not ideal, as it only lasts while the shell which executed the script stays open. The solutions I posted above both register a permanent event filter (which will continue running even if the PC is restarted), which is why I am looking at those methods. I updated my question to reflect this requirement. – Bassie Apr 27 '16 at 11:45
  • 1
    @Bassie Could you not run this as a scheduled task that never stops until you log off or something similar. That would be no different then other monitoring systems. I think yours and this are the only options really unless you consider 3rd party tools. – Matt Apr 27 '16 at 12:00
  • 1
    @Matt That is an option and I would consider it if it had some benefit over creating a permanent WMI event binding (see `.mof` method above), but the question I have is - which one is better / more efficient / more reliable? I am currently leaning towards `.MOF` rather than `FileSystemWatcher` because the latter requires both a script AND a scheduled task, whereas registering a permanent event only needs a script. However, I am not sure which is more reliable - maybe I should just set both up, create a bunch of files, and see which method triggered the most event actions – Bassie Apr 27 '16 at 12:06
1

Based on the discussion here, it would seem that there shouldn't be any issues in terms of reliability or performance when using these types of events.

The impact of the ActiveScript consumer is trivial. If you do not create an event that fires continuously then the event is innocuous. I have used thises vevents for almosttwo decades without issue when designed correctly.

The WQL event filter DOES NOT POLL. It use SENS events to generate a response. SENS has been part of Windows since at least W2K. It is based in the kernel. A great amount of Windows is based on SENS.

They also mention that while PowerShell can use around 100MB to start up, WMI event monitoring does not (and neither does VBSCript - the preferred scripting language for ActiveScriptEvent actions).

As I can't seem to find any information about this anywhere else (that is in terms of reliability/performance) I will have to go by this, and so will be implementing my folder monitor using WMI/WQL/MOF.

This article also has some useful information for anyone looking for more details. It also suggests that running this instead of some continuous application is a better use of system resources, however it doesn't mention whether this includes the use of scheduled tasks.

Community
  • 1
  • 1
Bassie
  • 9,529
  • 8
  • 68
  • 159