Monitoring and Archiving Newly Created Files

I’ve received an interesting question about how to archive new files every day in an office setting. The solution that I’ve come up for this reader involves the creation of an event-driven script that can monitor a folder for newly created files and copy them to another location.

Hi, I need help copying newly created files using WSH. Every day in my office new files are being created. I want copy those files into my server. […] How do I do this? Please help me, sir.
– Ramana R.

My first thoughts after reading this were that there are a couple of ways to tackle this. First, I could create a simple script to run daily as a Scheduled Task that would check the file creation dates for a folder and copy any files created within 24 hours. Then, I thought, “why not have some fun with it?” So, I decided that this would be much better as an event-driven script.

Event-driven scripting is a little known scripting technique that is based on event triggers. The script runs in the background–much like a service–and reacts each time a specific event occurs. (In this case, a file creation.)

Essentially, the script I’ve written watches a specified folder and waits until a new file has been created. When the event is triggered, it copies the file to another archive location. Of course, I’ve included all of the bells and whistles including some error-handling and logging.

To begin, you need to include a few details for the script. It needs to know what directory to watch (strSource) and where to copy files (strDest). Once you define a source, the script needs to know whether or not it should overwrite files if there is a naming conflict.

strSource = "C:\test"
strDest = "C:\test2"
bOVERWRITE = vbTrue

This script is designed to be run on a local machine, so the source path should be a full local path (or a mapped network drive). The destination, however, doesn’t have to be. If you want to archive files to another machine, such as a server, provide a fully qualified UNC path and be sure that the currently logged on user has write permission for that share.

Next we begin building the script. Since this script uses events provided by WMI, we’ll need to add a reference to that next.

strComputer = "."
Set objWMIService = GetObject("winmgmts:" & strComputer & "rootcimv2")

The next step is to write the WMI query that will provide our script functionality. Even those of you with some WMI scripting experience may find this query a bit complex. I’ll try to break it down a bit to make it easier. For a more thorough explanation, check out my articles on ASP Free.

Event Scripting with WMI
More Event Scripting with WMI

The WMI query we will be using relies on the __InstanceCreationEvent method provided by WMI. Essentially, this event is triggered whenever an object is created. That object could be a folder or registry key, or in our case, a file. The WMI Service also provides a special method for executing event-driven queries.

Set colEvents = objWMIService.ExecNotificationQuery _
	("SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE " _
		& "Targetinstance ISA 'CIM_DirectoryContainsFile' AND " _
		& "TargetInstance.GroupComponent= " _
		& "'Win32_Directory.Name=""" & formPath(strSource) & """'")

The ExecNotificationQuery method returns a collection of events based on the notification query that we provide. Our query basically looks for any file created whose directory path matches the one we’ve provided. The ‘WITHIN 10’ acts as a timer that tells WMI how often in seconds it should return an event collection.

Notice that I’m passing the folder path through a function called formPath. This is because WMI queries require all back slashes to be escaped. The method does that for me as you’ll see a little later on.

Since this type of query continually executes asynchronously in the background, we’ll house our script logic inside of an endless loop so that our script never exits. This way, it will continue to monitor for new events every ten seconds.

Do While True
	Set objEvent = colEvents.NextEvent()
	copyFile(objEvent.TargetInstance.PartComponent)
Loop

I’ve created an endless loop by using the Do While True statement. By specifying True, I ensure that my loop is always evaluated. Inside, I read the next item in the event collection returned by the WMI Service. Each time there is an event, I pass it off to the copyFile subroutine to be archived. If there are none, the script will patiently wait until one becomes available.

Sub copyFile(strFile)
	On Error Resume Next
	Set objFso = CreateObject("Scripting.FileSystemObject")
	Set objFile = objFso.GetFile(cleanPath(strFile))
	Err.Clear
	objFile.Copy strDest, bOVERWRITE
	If Err.number <> 0 Then logError objFso, objFile.Name
	Set objFile = Nothing
	Set objFso = Nothing
End Sub

The copyFile subroutine handles the archiving process. Quite simply, it performs a file copy. It accepts the file path returned by our WMI procedure and performs the copy using the FileSystemObject. This part of the script should look pretty familiar to those who have a little experience in scripting.

You will notice, however, that there are a few uncommon subtleties.

First and foremost, I’ve disabled the script host’s internal error trapping. I’ve done this so that the script will not be forced to exit if a file copy error occurs. Since the internal error-trapping has been disabled, I needed to handle it myself. I’ve chosen to log the copy error to a text file using a logError subroutine that you’ll see at the end of this article.

Now it’s time to revisit some procedures that were referenced earlier. We need to create the two functions for dealing with our paths.

Function formPath(strPath)
	formPath = Replace(strPath, "\", "\\")
End Function

The first is easy enough. It just takes a standard Windows path and adds additional backslashes to make so that it is escaped for use in a WMI query. VBScript’s Replace function makes quick work of this by finding every backslash and replacing it with two instead.

The second function is quite a bit trickier. For sake of brevity, I’m not going to explain it in great detail, but it does work!

Function cleanPath(strWMIpath)
	strPath = Right(strWMIpath, Len(strWMIpath) - InStr(strWMIpath, "="))
	strPath = Left(strPath, Len(strPath) - 1)
	strPath = Right(strPath, Len(strPath) - 1)
	strPath = Replace(strPath, "\\", "\")
	parsePath = strPath
End Function

WMI gives us a very strangely formatted path. This function cuts off the parts we don’t need, removes the additional escaping backslashes, and returns a clean Windows file path.

We wrap up this script by creating the subroutine that will log any errors. This is just a simple example that will append or create a text file in the same directory as the script with a timestamped list of any files that weren’t able to be copied.

Sub logError(ByRef objFso, strFile)
	Set objLogFile = objFso.OpenTextFile("errorlog.txt", 8, vbTrue)
	objLogFile.WriteLine Now() & " Error copying " & strFile
	objLogFile.Close
End Sub

There are two things I’ve done here that probably have you scratching your head a little. You’ve probably noticed that I passed the file system object to this subroutine. You don’t generally see that done very often. Why wouldn’t I just create it globally? Second, you’re probably wondering what on earth that ByRef statement is for, right? Let’s take these one at a time.

I’ve chosen not to create the FileSystemObject globally because this script is designed to run in the background all of the time. By only creating it when I need it, I can help control the overhead that this script requires to run. That’s the same reason I’ve chosen to pass this object to the logError function so that I could prevent having to create an unnecessary duplicate.

I know what you’re thinking. Passing an object to another procedure would create a duplicate because the two procedures are in different scopes. True, but that’s where that ByRef statement comes in to play. It allows us to pass the object’s actual memory reference from one scope to another.

With all of these pieces in place, our script is complete. Take a look at the next page to see the script in its entirety.

strSource = "C:\test"
strDest = "C:\test2"
bOVERWRITE = vbTrue
 
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & strComputer & "rootcimv2")
 
Set colEvents = objWMIService.ExecNotificationQuery _
	("SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE " _
		& "Targetinstance ISA 'CIM_DirectoryContainsFile' AND " _
		& "TargetInstance.GroupComponent= " _
		& "'Win32_Directory.Name=""" & formPath(strSource) & """'")
 
Do While True
	Set objEvent = colEvents.NextEvent()
	copyFile(objEvent.TargetInstance.PartComponent)
Loop
 
Sub copyFile(strFile)
	On Error Resume Next
	Set objFso = CreateObject("Scripting.FileSystemObject")
	Set objFile = objFso.GetFile(cleanPath(strFile))
	Err.Clear
	objFile.Copy strDest, bOVERWRITE
	If Err.number <> 0 Then logError objFso, objFile.Name
	Set objFile = Nothing
	Set objFso = Nothing
End Sub
 
Function formPath(strPath)
	formPath = Replace(strPath, "\", "\\")
End Function
 
Function cleanPath(strWMIpath)
	strPath = Right(strWMIpath, Len(strWMIpath) - InStr(strWMIpath, "="))
	strPath = Left(strPath, Len(strPath) - 1)
	strPath = Right(strPath, Len(strPath) - 1)
	strPath = Replace(strPath, "\\", "\")
	parsePath = strPath
End Function
 
Sub logError(ByRef objFso, strFile)
	Set objLogFile = objFso.OpenTextFile("errorlog.txt", 8, vbTrue)
	objLogFile.WriteLine Now() & " Error copying " & strFile
	objLogFile.Close
End Sub

CopyNewFiles.vbs

Tags

Like the read? Share it!

1 Comment

  • One of the most useful websites I’ve come across for advice within this specific specialized niche. I’ll be looking back constantly for brand new posts.

Leave a Reply

Contact

Wanna say hello?
Drop us a line!

You'll find us here

1 Microsoft Way,
Redmond,
WA 98052, United States