Freigeben über


Files and Folders, Part 2

By The Microsoft Scripting Guys

Sesame Script

Welcome to Sesame Script, the column for beginning script writers. The goal of this column is to teach the very basics of Windows scripting for system administration automation. We’ll provide you with the information you’ll need to begin reading and understanding scripts and to start modifying those scripts to suit your own needs. If there’s anything in particular about scripting you’re finding confusing, let us know; you’re probably not alone.

Check the Sesame Script Archives to see past articles.

Special Offer! Download all the Sesame Script columns (from the very first column through the June, 2007 edition) in one easy-to-read, fully-searchable .chm file.

On This Page

Prologue
Files and Folders in WMI
Check for Files and Folders
Copy Files and Folders
Move Files and Folders
Delete Files and Folders
Copy by Date
The Moral of the Story
Epilogue

Prologue

The Scripting Guy who writes this column is a little cranky today. This Scripting Guy let the Scripting Dog out in the fully-fenced backyard around midnight last night, just before going to bed. Just as the Scripting Guy opened the door, the Scripting Dog went running into the yard - and suddenly everything went crazy and the Scripting Guy wound up trying to keep the Scripting Dog from eating the neighbors’ cat. The neighbors who, by the way, just moved in two days ago. Welcome to the neighborhood.

With that in mind, you can probably understand why we’ve decided to write the exact same article this month as we did last month. Enjoy.

Okay, that’s not entirely true. The topic is the same - files and folders - but the article is going to be different. For starters, we’re not even going to mention the word subrogation. Besides that, last month we looked at how to work with files and folders using the FileSystemObject. This month we’re going to show you another option, the Windows Management Instrumentation (WMI) Win32_Directory and CIM_DataFile classes.

Files and Folders in WMI

Before we start, we’ll mention that there are several advantages to using the FileSystemObject, including ease of use, speed, and simple date manipulation. But there are also disadvantages, one big one being that the FileSystemObject doesn’t work on remote computers while WMI does. So this month we’re going to follow pretty much the same format that we followed last month, highlighting some of the main differences in working with these two types of file and folder manipulation. So, just like last month, we’re going to start by determining whether or not a file or folder even exists.

Check for Files and Folders

You might have noticed that we mentioned two classes: Win32_Directory and CIM_DataFile. Unlike the single FileSystemObject class, which deals with both files and folders, WMI uses two separate classes: CIM_DataFile is for working with files, and Win32_Directory is for working with folders. The properties for both are pretty much the same, but we’ll show you how to work with each of them anyway.

In our first script, we’re going to show you how to determine whether a particular folder exists. We do that like this:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFolders = _
    objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'")

If colFolders.Count < 1 Then
    Wscript.Echo "Folder does not exist"
Else
    Wscript.Echo "Folder does exist"
End If

As we mentioned, we can use these scripts to work with files and folders on remote computers, so the first thing we do is set the strComputer variable to the name of the computer we’re going to be working with. In this case we’ve used a dot (.) to signify the local computer:

strComputer = "."

If we had wanted to look for the folder on a different computer, for example, a computer named atl-fs-01, we would have assigned that computer name to strComputer:

strComputer = "atl-fs-01"

Next we connect to the WMI service:

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Don’t worry too much about the details here; the important thing to know is that this line connects to the WMI service so that we can start working with WMI classes. And that’s exactly what we’re going to do. We want to find out about a particular folder, or directory, on a computer. To do that in WMI we need to create a query that retrieves folder information. Here’s the query we used:

Select * From Win32_Directory Where Name = 'C:\\Scripts'

If you have questions about how WMI queries work, take a look at this Sesame Script article. However, we will do a quick walkthrough here, just to make sure everyone understands what we’re talking about. We start with “Select *” which means “Return all the information about the class we’re going to query.” “From Win32_Directory” says we want to get the information about the Win32_Directory class. “Where Name = ‘C:\\Scripts’” says we want only the Win32_Directory objects that have a Name property that is equal to C:\Scripts. Yes, we know there are two backslash characters in there; we did that on purpose. If you don’t use two of them the query won’t work.

Note. That’s always the case with backslashes and WMI queries. Because the backslash is a reserved character in WMI you must “escape” it any time the backslash is used in a Where clause. And because WMI’s escape character is also a backslash, that results in you needing to preface each backslash with a second backslash. Thus C:\\Scripts.

We pass this query as a parameter to the ExecQuery method, which runs the query and returns the result:

Set colFolders = _
    objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'")

The results of our query are now in the collection colFolders, meaning we’ve stored all the instances of the Win32_Directory class with the Name C:\Scripts in the colFolders collection. As most of you know, folders within a file system must be unique, so the fact that we’ve asked for a specific folder name means we should have, at most, one item in our collection. We can find out for sure how many items are in the collection by checking the collection’s Count property:

colFolders.Count

The whole point of this script was to find out whether the C:\Scripts folder exists. Well, if Count is equal to 1, that means our query found C:\Scripts; if it’s less than 1 then query didn’t find this folder. So we simply put this check in an If statement and echo back the results:

If colFolders.Count < 1 Then
    Wscript.Echo "Folder does not exist"
Else
    Wscript.Echo "Folder does exist"
End If

It’s pretty easy to read along with the script at this point: if colFolders.Count < 1 (if there were no objects returned from our query for C:\Scripts) then echo back a message saying the folder doesn’t exist. Otherwise (Else) the query returned at least one object, so we echo back a message that the folder does exist.

So there’s our folder. Now what about a file? Well, take a look:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'")

If colFiles.Count < 1 Then
    Wscript.Echo "File does not exist"
Else
    Wscript.Echo "File does exist"
End If

If you’ve been paying any attention at all this should look very familiar. (How could you not be paying attention? Grab a cup of coffee and try again.) This script starts just like the last one, connecting to the WMI service on the local computer. Then comes the query:

Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'

This time, instead of querying for the Win32_Directory class, we’re querying for the CIM_DataFile class:

Select * From CIM_DataFile

Our Where clause is a little bit different, too. Notice we’re still looking for the Name property, but this time instead of a folder name we give it a file name (including the full path):

Where Name = 'C:\\Scripts\\Test.txt'

We once again run the query using the ExecQuery method. This time instead of a collection of folders, we’ll receive a collection of files. And because we’re looking for one particular file, we’ll have, at most, one item in the collection. So we - again - use the Count property to determine whether we found any files named C:\Scripts\Test.txt and echo back the appropriate message:

If colFiles.Count < 1 Then
    Wscript.Echo "File does not exist"
Else
    Wscript.Echo "File does exist"
End If

See? It took very little to go from checking for the existence of a folder to checking for a file. You’ll see this pattern continue as we go through the rest of the article.

Copy Files and Folders

We’re going to talk about folders first, so, technically, that heading is wrong; it should be “Copy Folders and Files.” But that sounds weird to us, plus we’d have to go back and change the last heading too, and that’s way too much trouble for an editorial technicality; therefore, we’re leaving it as Files and Folders. If that (or the fact that that last sentence was way too long) bothers you, write to your local city officials. Just don’t write to us about it; we have enough to worry about (like unhappy neighbors).

Note: We told you we were a little cranky today. Okay, the Scripting Guy who writes this column is a little cranky today. The other Scripting Guy, who didn’t have to watch the Scripting Dog try to eat a cat, says he wants people to write to us. Maybe not about article headings, but anything else would be fine.

All right, enough of that. Here’s a script that copies the entire contents of a folder to another folder:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFolders = _
    objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'")

For Each objFolder in colFolders
    objFolder.Copy("C:\Temp")
Next

This script starts like most WMI scripts, connecting to WMI on the local computer. Next we run the exact same query we ran earlier, the one that gets all the instances (“all” in this case meaning “one”) of the folder C:\Scripts. Nothing new so far, and there really isn’t much more to this script. We set up a For Each loop to loop through the collection. The first (and only) time through the collection we grab the C:\Scripts folder (stored in the objFolder variable). We then call the Copy method on that folder, passing in the full path to the folder we want to copy to:

For Each objFolder in colFolders
    objFolder.Copy("C:\Temp")
Next

There are a couple of things you need to know before trying this out yourself. The first is that the folder name you pass to the Copy method must be a folder that doesn’t exist - the Copy method will create the folder for you. If you pass an existing folder, the script will run without displaying any errors, but it won’t actually copy anything.

Note: You can check for errors by capturing the return value from the Copy method. You do that like this:

errResult = objFolder.Copy("C:\Temp")

You can then echo back the value of errResult, and compare that value to the error codes in the WMI SDK.

The other thing you should know is that if your source folder, in this case C:\Scripts, contains any subfolders, those subfolders won’t be copied. If you want to copy all the files and folders within a folder, you need to use the CopyEx method:

For Each objFolder in colFolders
    objFolder.CopyEx "C:\Temp",,,True
Next

The Copy method takes only one parameter: the name of the folder we’re copying to. CopyEx, on the other hand, takes four parameters, although we’re only concerned with the first and the last. The first parameter is the destination folder. After that we leave blank spaces for parameters two and three, putting in commas the show that, yes, we know there is a parameter two and a parameter three, we’re just not using them. We then assign the value True to the fourth parameter. Setting this parameter to True tells CopyEx that we want this to be a recursive copy, meaning we want to copy not only all the files in the folder, but all the subfolders and their files, and all the subfolder’s subfolders, etc.

Note: VBScript has one interesting feature that you might want to know about at this point. If you call a method but don’t capture the return value, you can’t put parentheses around the parameters. For example, in our call to CopyEx there were no parentheses:

objFolder.CopyEx "C:\Temp",,,True

But if you wanted to capture the error code that’s returned from CopyEx, you need to add the parentheses:

errResult = objFolder.CopyEx("C:\Temp",,,True)

It’s a little strange, but that’s the way it works.

All right, we’ve copied an entire folder, but what if we want to copy just one file? Okay:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'")

For Each objFile in colFiles
    objFile.Copy ("C:\Temp\NewTest.txt")
Next

We once again connect to WMI on the local machine, then run our query. This time we’re querying against the CIM_DataFile class, and we’re querying for the name of the file we want to copy:

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'")

Now we loop through our collection of files (in this case our collection that contains just one file) and call the Copy method, passing it the path to the new file we want to copy to:

For Each objFile in colFiles
    objFile.Copy ("C:\Temp\NewTest.txt")
Next

In this example we gave the file a new name, but you don’t have to do that; you can give it the same name it had. Whatever name you give it, that file can’t exist in the destination folder before you call this method. The Copy method won’t overwrite an existing file.

You probably have several questions at this point, and we’re sure one of them is “So how do I copy multiple files?” If you weren’t thinking of that question, don’t worry, you were going to at some point. (No, we’re not psychic. If we were we would have made sure the cat left the yard before letting the Scripting Dog out. But there are some things we just know - like the fact that cats shouldn’t be in the Scripting Dog’s yard.) Here’s how you copy all the .txt files from the C:\Scripts folder to the C:\Temp folder:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'")

For Each objFile in colFiles
    strNewFile = "C:\Temp\" & objFile.FileName & ".txt"
    objFile.Copy strNewFile
Next

We’re looking for files, so we once again use the CIM_DataFile class. This time instead of looking for a specific filename, we’re looking for all files in our C:\Scripts folder that have a file extension of .txt. To find those files, in the Where clause of our query we look for files where the Path property is equal to \\Scripts\\ and the Extension property is equal to txt:

Where Path = '\\Scripts\\' and Extension = 'txt'

Notice first that once again we need to put double backslashes in our path. Also notice that there seems to be a couple of things missing. The first is the drive letter. We’re assuming that we have only one drive, and that the Scripts folder is on that drive. If we wanted to look specifically for the C: drive, we’d need to add another section to our Where clause to check for the Drive:

Where Drive = 'C:' and Path = '\\Test\\' and Extension = 'txt'

Another thing that seems to be missing is the dot (.) in the file extension. But it’s not missing, it’s supposed to be that way; the Extension property contains only the file extension itself (txt), not the dot (.txt).

At this point we’ll have a collection of all the .txt files in our C:\Scripts folder. Now all we have to do is set up a For Each loop to run through the collection and copy the files.

But wait a minute, there’s one more thing we need to do before we can copy the files: we need to know the names of the files. In our earlier script we copied one file; we knew the name of the file we were copying and were able to give it a name at its new location. But here we’re getting all the .txt files and we have no idea what the individual name of each file is. Therefore, we string together a name that consists of the existing file name and the folder we’re copying the file to, and use that in our Copy method:

strNewFile = "C:\Temp\" & objFile.FileName & ".txt"

We’re copying the file to the C:\Temp folder, so we start with that. We then append the FileName property of the file, which just happens to be holding the name of the file - without the extension. Because FileName doesn’t include the extension, we tack that onto the end. Now it’s time to copy the file:

objFile.Copy strNewFile

That’s really all there is to copying. Yes, it’s a little more complicated than the FileSystemObject, but that’s the price you pay for working on remote machines.

Move Files and Folders

In our discussion of the FileSystemObject, this is where we included a section on moving files and folders. That made sense because the FileSystemObject includes methods to do just that. However, WMI doesn’t include move methods with either Win32_Directory or CIM_DataFile. So how do you move files rather than copy them? You copy them from the source location to the destination, then delete them from the source.

That means it’s time to move on to the Delete section.

Delete Files and Folders

After everything we’ve seen so far, deleting is really easy. Let’s delete a file:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'")

For Each objFile in colFiles
    objFile.Delete
Next

We do all that stuff up front that we always do (connecting to WMI and so on), then we run our query to find the file we want to delete, in this case C:\Scripts\Test.txt:

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Name = 'C:\\Scripts\\Test.txt'")

Once we have a collection with that file in it, it’s a simple matter of going through the For Each loop to get that one file from our collection and delete it:

For Each objFile in colFiles
    objFile.Delete
Next

As you can see, we call the Delete method on the file and we’re done.

All right, that’s nice enough, but let’s say we want to delete an entire folder. You might find this hard to believe (and if you do we don’t know where you’ve been while everyone else was reading this article) but the Win32_Directory class also has a Delete method:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFolders = _
    objWMIService.ExecQuery("Select * From Win32_Directory Where Name = 'C:\\Scripts'")

For Each objFolder in colFolders
'    objFolder.Delete
Next

This script is identical to the previous one, with just a couple of exceptions:

  • We use Win32_Directory in our query rather than CIM_DataFile

  • We specify only the folder (C:\Scripts) rather than a file name (C:\Scripts\Test.txt)

  • We commented out the actual Delete command so you don’t accidentally do something you’ll regret. If you really do want to delete your entire C:\Scripts folder and everything in it, remove the comment mark (') from this line:

    '    objFolder.Delete
    

Other than that it’s the same. Run this script and your C:\Scripts directory will be deleted, along with all of its contents. And, unlike the FileSystemObject, WMI doesn’t care if you have read-only files in your folder; it’s going to delete everything.

Note. Well, unless this folder contains subfolders; then things can get a little tricky. For more information on deleting a folder that contains subfolders take a look at this Hey, Scripting Guy! column.

How about one more in the delete category? Let’s delete all the .txt files from the C:\Scripts folder:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'")

For Each objFile in colFiles
    objFile.Delete
Next

Just like in the Copy example, we’re querying the CIM_DataFile object for all files in the \Scripts folder with a file extension of txt. We then use a For Each loop to loop through that collection of files and delete each one. Note that our query returns only files in the \Scripts folder, not files in any subfolders. That means if you have a folder named C:\Scripts\Temp with a .txt file in it, that file won’t be deleted with this script; you’ll need to check subfolders separately.

Copy by Date

We’re going to use the same example we used last time: Copy all the files from C:\Scripts that were created more than a month ago to the C:\Scripts\Old folder. You’ll notice very quickly that this is a more complicated in WMI than it was when we used the FileSystemObject.

Both the CIM_DataFile class and the Win32_Directory class have a property named CreationDate, a property that contains the date the file or folder was created. Let’s first take a look at a script that simply echoes the creation date of every .txt file in the C:\Scripts folder:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'")

For Each objFile in colFiles
    Wscript.Echo objFile.CreationDate
Next

This script will return output that looks something like this:

20070523191310.800177-420
20070514115306.449240-420
20070523191249.896177-420
20070523191323.217777-420
20070406160656.929217-420

Yes, believe it or not, these are the dates that each .txt file in the C:\Scripts folder was created. They don’t look much like dates do they? But they are; they’re WMI dates, which are stored in UTC format. And the thing about WMI dates is that they’re not the same as VBScript dates (or any date that someone could easily look at and figure out). That means we can’t just compare these UTC dates to standard VBScript system dates. Instead, we’re going to get a little fancier here.

To explain what that means, let’s take a look at the first date in this list:

20070523191310.800177-420

If you look at the first four characters, 2007, you’ll find the year. The next two characters, 05, are the month. The two characters after that, 23, are the day. The rest of the string is the time, with the -420 at the end being the offset from Greenwich Mean Time (GMT). So our date is May 23, 2007. Now let’s say we receive the current date from VBScript using the Date method:

Wscript.Echo Date

If today is June 6, 2007, this statement will echo back (depending on your regional settings) a date like this:

6/6/2007

If we were to compare these two dates, we wouldn’t get the results we would hope for:

For Each objFile in colFiles
    Wscript.Echo Date
    Wscript.Echo objFile.CreationDate
    If Date > objFile.CreationDate Then
       Wscript.Echo "Current date is greater"
    Else
       Wscript.Echo "Current date is less"
    End If
Next

Here’s what we’d get back for that first date:

6/6/2007
20070523191310.800177-420
Current date is less

June 6, 2007 is less than May 23, 2007? Not on our calendar.

All of this shows you that we can’t compare the dates as-is; instead, we need to do a little work to get them to come out right. With that in mind, here’s a script that copies all the .txt files in the C:\Scripts folder that were created more than a month ago to the C:\Script\old folder:

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'")

dtMonthAgo = DateAdd("m", -1, Now)

For Each objFile in colFiles
    dtCreationDate = WMIDateStringToDate(objFile.CreationDate)
    If dtCreationDate < dtMonthAgo then
        strNewFile = "C:\Scripts\old\" & objFile.FileName & ".txt"
        objFile.Copy strNewFile
    End If
Next

Function WMIDateStringToDate(dtmInstallDate)
    WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) & "/" & _
        Mid(dtmInstallDate, 7, 2) & "/" & Left(dtmInstallDate, 4) _
            & " " & Mid (dtmInstallDate, 9, 2) & ":" & _
                Mid(dtmInstallDate, 11, 2) & ":" & Mid(dtmInstallDate, _
                    13, 2))
End Function

After connecting to WMI on the local computer we run the query that retrieves all the .txt files in our C:\Scripts folder:

Set colFiles = _
    objWMIService.ExecQuery("Select * From CIM_DataFile Where Path = '\\Scripts\\' and Extension = 'txt'")

Next comes this line:

dtMonthAgo = DateAdd("m", -1, Now)

Here we’re using the VBScript DateAdd function to determine the date exactly one month ago. See last month’s article for a full explanation, but, briefly, we’re using this function to subtract one (or add -1) to the month (“m”) part of the current date (Now). We’ll store that date in the variable dtMonthAgo.

Next we start our For Each loop to loop through all the .txt files:

For Each objFile in colFiles

The first thing we do within this For Each loop is retrieve the creation date from objFile.CreationDate. We pass this date to a function named WMIDateStringToDate:

dtCreationDate = WMIDateStringToDate(objFile.CreationDate)

We’re not going to go over this function in detail; we already did that in the Sesame Script article on dates. All this function does is turn a WMI date like this:

20070523191310.800177-420

Into a date that looks more like a date:

5/23/2007 19:13:10

We then store the newly-converted date in the dtCreationDate variable. Now that we have dates in the same format, we can compare then without any problems:

If dtCreationDate < dtMonthAgo then

If the creation date is less than the date one month ago (meaning the file was created more than a month ago) we copy the file to the C:\Scripts\old folder:

strNewFile = "C:\Scripts\old\" & objFile.FileName & ".txt"
objFile.Copy strNewFile

We once again need to put together the file name before we can copy it, but at this point we’re done. If the creation date isn’t more than a month ago we simply ignore that file and move on to the next one.

That’s all there is to copying files based on the date. And in case you were wondering, Win32_Directory and CIM_DataFile have properties that return other dates too: InstallDate, LastAccessed, and LastModified. Just something you might like to know.

The Moral of the Story

The moral is, if you want to work with files and folders on the local machine, the FileSystemObject might be a little easier. But if you want a little more flexibility and you need to work on remote computers, WMI is the way to go.

That’s not actually a moral is it? It’s really more of a summary. Okay, here’s a better one. Before you let your cat outside, make sure it’s healthy enough to clear a 6-foot fence so it doesn’t get eaten by an old arthritic dog who can’t even catch her own tail.

Epilogue

For anyone out there concerned about the cat, the next morning it was hiding in the bushes (where no one could reach it) and wouldn’t come anywhere near the Scripting Guy’s house or even the neighbor’s house. But it seemed to be in one piece and is expected to recover nicely.

For anyone concerned about the Scripting Dog - she’s pretty proud of herself for her brave defense of her territory and is currently at this Scripting Guy’s feet snoring soundly.