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.
Creating a WMI WQL event query
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:
- Immediately after the opening tag @", hit ENTER. Do not press the spacebar and then ENTER; you need the return right after the @".
- The closing tag ("@) must be in position 1 of its own line. You cannot place it at the end of your last line of text, nor can you indent to make things “line up.” It must be in the first position of its own line.
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= " _
& "'Win32_Directory.Name=""c:\\\\scripts""'")
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"'
"@
Register the WMI event
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.
$fileEvent.SourceEventArgs.NewEvent.TargetInstance.PartComponent
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.
Clean up after creating a temporary event subscriber
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.
Function Remove-WMIEventAndSubscriber
{
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.
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 scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy