Learn about Windows PowerShell
Summary: Microsoft Scripting Guy, Ed Wilson, shows how to use Windows PowerShell to monitor for the creation of new files.
Microsoft Scripting Guy, Ed Wilson, is here. Yesterday’s email from KS about his problems with files that contain leading spaces in them got me thinking. Although running a script on demand to find and rename files in a folder might work, it would be better to use an event to monitor the folder for newly created files. Then if the files match the naming pattern discovered yesterday, rename them by using the procedure from the script I posted yesterday in Use PowerShell to Detect and Fix Files with Leading Spaces.
Note For more information about WMI event driven scripts, see An Insider’s Guide to Using WMI Events and PowerShell.
Today I am going to develop a WMI event query to detect newly created files in a particular folder. Then I will use this WQL event query tomorrow to create a permanent WMI event consumer. In fact, whenever I am creating a permanent WMI event consumer, I always test it out as a temporary event consumer first. Creating a temporary event consumer with Windows PowerShell 2.0 is really easy, so it only makes sense to take this first step.
The hardest part of creating a WMI WQL event query is, well…just about everything. This stuff does not make much sense. Luckily, if you have WQL event query from VBScript or some other language, it is not too difficult to migrate the query to Windows PowerShell.
When you start trying to do this, however, you run into weird quoting rules that only make a confusing situation more confusing. Luckily, Windows PowerShell can bring some sanity to this part of the process. The secret is to use a here-string. Here-strings are really finicky (they make Morris the Cat seem like an omnivore). The basic syntax is to use a variable to hold the resulting here-string. The here-string begins with an ampersand and an opening quotation mark: @". Everything inside the here-string is interpreted literally so you do not need to worry with escaping special characters or quotation marks or any of that stuff. The here-string closes with a closing quotation mark ampersand: "@.
There are two rules that you must follow:
I referred to an old Hey Scripting Guy! Blog, How Can I Automatically Run a Script Any Time a File is Added to a Folder, which was written nearly eight years ago in VBScript. Guess what? The query was just the thing I needed to refresh my memory for creating my new query. Here is the VBScript query from that blog.
("SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE " _
& "Targetinstance ISA 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
You can see where the use of a here-string vastly simplifies things by allowing me to forget about line continuation and having to escape quotation marks and other things. But also you can see how having a nice reference query, even from an eight-year old VBScript script, is also beneficial.
Note This is ONE of the major reasons I insisted on migrating all of the old Hey Scripting Guy! Blogs to the new Hey, Scripting Guy! Blog format four years ago when I became the Scripting Guy. I knew that a lot of that old code was easily adaptable to Windows PowerShell and would be useful for years to come.
The WQL query itself is not too horribly bad. It begins by selecting everything from the __InstanceCreationEvent WMI class. This class is a generic event class, and it will monitor for new instances of “stuff.” It can be anything from a new entry in an event log to a new file. The problem with monitoring for a newly created file is that a file must reside somewhere—for example, inside a directory. To find a file in a directory by using WMI means that we need to use an association WMI class.
The Cim_DirectoryContainsFile WMI class associates files and directories. When working with association classes, there is always a property that relates one to the other. Here we are looking for the GroupComponent portion of the association. GroupComponent is an instance of the Win32_Directory WMI class. Because we are interested in a particular directory, we need to use the Key property for GroupComponent. Here, the key is the name of the folder. The name of the folder uses POSIX notation; therefore, it requires \\\\ (four back slashes). The query is shown here.
$query = @"
Select * from __InstanceCreationEvent within 10
where targetInstance isa 'Cim_DirectoryContainsFile'
and targetInstance.GroupComponent = 'Win32_Directory.Name="c:\\\\test"'
Now I need register the WMI event. In Windows PowerShell 2.0 and Windows PowerShell 3.0, this is really easy. I use the Register-WmiEvent cmdlet and specify the WQL query. I also need to create a value for the SourceIdentifier property so I can monitor the job. Here, I register the WMI event by using the query contained in the $query variable, and I specify a SourceIdentifier of MonitorFiles.
Register-WmiEvent -Query $query -SourceIdentifier "MonitorFiles"
Upon registering the event, I can do any number of things. The easiest thing to do is to wait for the event to occur. The Wait-Event cmdlet will wait for the event that is identified by the SourceIdentifier to trigger. After it does, I store the generated event in the $fileEvent variable as shown here.
$fileEvent = Wait-Event -SourceIdentifier "MonitorFiles"
Once again, I could do anything I want to do upon notification that an event triggers. Here, I simply display the complete path to the newly created file. I will use this information tomorrow in my follow-up to today’s blog. Notice that the $fileEvent variable contains a rich object. You might want to play around with Get-Member to explore this object.
When the script runs, it waits for an event to trigger. This behavior is shown in the image that follows.
When I create a file in the c:\test folder, within 10 seconds the temporary event consumer detects the presence of the newly created file, and Wait-Event returns the event to the $fileEvent variable. The script then displays the path to the newly created file. The image that follows illustrates the Windows PowerShell ISE following the generation of the new event.
If you attempt to run the script a second time, you will more than likely receive errors. The error is because the event SourceIdentifier “MonitorFiles” already exists. The way to correct this is to unregister the event. You can do this by name, by specifying the SourceIdentifier property of the Unregister-Event cmdlet. But the easier way to do this is to use the Get-EventSubscriber cmdlet, and pipe the event subscriber to the Unregister-Event cmdlet as shown here.
Get-EventSubscriber | Unregister-Event
The unpredictable results portion of the scenario is that one WMI event already exists—the one that generated during testing. It is certainly possible to work with multiple events, but it is also easier to just clean up. The easiest way to do this is to find all of the WMI events by using the Get-Event cmdlet, and then pipe all the found WMI events to the Remove-Event cmdlet. This command is shown here.
Get-Event | Remove-Event
If you are writing a temporary WMI event consumer script, it makes sense to place the two previous commands into a function called something like Remove-WMIEventAndSubscriber. Such a function is shown here.
Get-EventSubscriber | Unregister-Event
Get-Event | Remove-Event
} #end function Remove-WmiEventAndSubscriber
A function such as Remove-WMIEventAndSubscriber makes testing your script inside the Windows PowerShell ISE much easier, and it saves a lot of typing because you reset the environment each time you decide to run an additional test.
The complete TemporaryWMIEventToMonitorFolderForNewFiles.ps1 script is uploaded to the Scripting Guys Script Repository. You can download the entire script from there.
That is all there is to using a temporary WMI event to monitor a folder for the creation of a new file. Join me tomorrow for more Windows PowerShell cool stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
Why you didn't include full source code of your script in a post?
Of course, for most purposes Process Monitor is far more powerful and detailed (eg. see individual IO operations with call stacks etc.)
Is it possible to monitor more than 1 folder per event? Also is it going to monitor the top level folder only or can it do subfolders as well?
Sorry about not including the full source code in the posting. I have uploaded the script to the TechNet Script Repository and added a sentence to the end of the article that points to that code. Thank you for pointing this out to me. Sorry for the inconvience.
@Rjcox you are absolutely correct ... this is not a replacement for process monitor from Sysinternals. But, this IS in the box, and the Sysinternals tools are not, and for quick things IF you know this technique it will come in useful. In addition, this exact technique can monitor for nearly any of the thousands of WMI classes, and so from that perspective it is more flexible. In addition, this article is a building block, and later in the week (like on Friday) I will turn this query into a permenant event ... and so it will monitor all the time.
@hallstev5 no. you will need one event per folder. Using this exact query it only montors the top folder.
I know this could monitor local drive. Is it possible to monitor a ftp site?
@Rick on a Windows Server FTP is a folder, and therefore yes it would monitor a FTP site.
I've already seen this kind of FileSystemWatcher ( which is a .Net framework object that could be used to watch file events) using WMI somewhere on this blog before. Maybe I remember the old VBScript version but I'm not that sure ... I didn't find it by now :-)
Anyway ... it's great what we can do with WMI events and I'm sure that is much more to find out.
But the difficulty is "how to find out what we can do and how it could be done" ( I'm sure that there will be some documentation on MSDN or other sites ... but I hardly would ever guessed how to associate the classes and what the required classes are. This is the hardest part.
The "$fileEvent.SourceEventArgs.NewEvent.TargetInstance.PartComponent" is such a thing.
Who would ever have found out, that this is the way to get at the name of the newly created file !?
This is the voodoo part of it ... (un)Registering and waiting for the event is quite easy with PS 2.0 (or 3.0)
@K_Schulte Yes, I like the .NET Filesystem watcher, and have written some PowerShell scripts using it. Finding this stuff? Well as you said it is documented in MSDN, and in my WMI book. A blog is also documentation? Right? But one of the great things about PowerShell is that it is self discovering, and it also makes it easy to take an idea and test it out. In the old days, when you had to horribly complicated VBScripts it did not make it possible to experiment to easily.