Set Archive Bit Based On Creation Date in WSH

Properly constructed incremental backup schemes use a file’s archive bit to determine what files to include in each backup increment. Currently indexed files should have the archive bit set, while modified or newly created files should have it unset to mark them for inclusion in the next backup. The following code sample can be used to set the archive bit based on a file’s creation date in WSH.

If you’ve installed the Windows Backup program in Windows XP, it will set archive bits based on age. The default will set an archive bit on any file older than 90 days so this example will do the same.

The code sample we will be creating will look into a provided folder and enumerate all files in that folder. It will then move through each file, check for the existence of an archive bit, and set it if the file is older than the set number of days.

The first step in creating this code sample will be to establish our constants. This includes the number of days that represents an archived file’s age (90 days) and the folder where the script should operate. This is done quite simply by assigning a couple of variables.

numDays = 90
strPath = "C:WindowsTemp"

Next, we’ll instantiate the FileSystemObject and check to see that our folder path really exists.

Set objfso = CreateObject("Scripting.FileSystemObject")
If objfso.FolderExists(strPath) Then 'Make sure folder exists

If is does in fact exist, we should instantiate the folder object using the FileSystemObject’s GetFolder method and enumerate its file collection with the Folder object’s Files property. Keep in mind that this should all be done inside of the IF statement to prevent any errors that would occur if the path did not exist.

   Set objFolder = objfso.GetFolder(strPath)
   Set colFiles = objFolder.Files 'Enumerate files

Once we’ve created the collection of files, we should move through each of them with a For Each loop and call a subroutine that will do the work of checking and setting the archive bit. We’ll be creating this function later.

Again, to prevent errors, we should nest the For Each loop inside of an IF statement that verifies that the file collection is not empty. We can do this simply by checking that the collection’s count property does not return 0.

   If colFiles.Count > 0 Then
      For Each objFile In colFiles
         Call SetArchiveFlag(objFile, numDays) 'Call subroutine for each file found.
      Next
   Else 'colFiles collection was empty
      WScript.Echo "No files in folder", objFolder.Path
   End If

In this example, I’ve used the Else branch of our If statement to display a message box if there are no files in the file collection. This should be replaced with logging code or any other appropriate error-handling code.

The same is true as we complete our first IF statement. This Else branch will execute if the supplied folder path was not valid.

Else 'Folder path could not be found
   WScript.Echo "The specified folder", strPath, "does not exist"
End If

That completes the basic code construct. This could be incorporated into a class or function in a larger script. Now we need to create the subroutine that will actually check and set the archive bit on each file.

You may be wondering why I’ve chosen to pass the numDays variable every time I call the SetArchiveFlag subroutine in the code example from the previous page. The reasoning is simple: flexibility. I wanted this code sample to be universal. By doing this, I’ve allowed the file age to be set at a folder level rather than globally. By recreating the previous code segment, you can use different age settings on different folders without recreating the entire code sample in your script.

This is where the dirty work begins. Anytime you’re dealing with file properties, it’s a good idea to disable script debugging that can halt execution of your script if errors are generated. We’ll make sure to check and respond to our own errors.

Unless your are fully handling all errors in your script, you should only disable this feature for small sections of your script. In this case, I’ve only disabled script debugging within this subroutine 1)You may also notice that I never turned script debugging back on. This was not an oversight. By design, script debugging will be enabled again as soon as execution leaves the subroutine..

Sub SetArchiveFlag(objFile, numDays)
   On Error Resume Next 'Disable error handling
   Err.Clear

Next, we’ll check the date of the file, using the File object’s DateCreated property, and establish a variable containing the current date of execution using VBScript’s Now function. All of the File object’s date properties 2)DateCreated, DateLastModified, and DateLastAccessed. and VBScript’s Now function return values of type Date. This will make things easier for us later. Since they are numbers of the same type, VBScript will allow us to perform calculations on them without first converting them to a standard number type.

   dateFile = objFile.DateCreated
   'Can also use DateLastModified or DateLastAccessed

   dateToday = Now()

Now we’ll check the age of the file, but before we do, let’s not forget that we’ve disabled script debugging. We want to be sure to capture any possible errors that our script could encounter. Do do this, we should encapsulate our code segment in an IF statement that checks to be sure that our file’s date is in fact less than today. This would indicate either a problem in the system clock or an incorrect date stamp–presumably that latter.

“But what if the file was created today?” You ask.

Remember that date stamps include data down to the second. The chances that a file was created at the exact second this code executed are very slim to none.

   If dateFile < = dateToday Then
      daysOld = dateToday - dateFile 'Calculate file age
      If daysOld < numDays Then
         strFile = objFile.Path 'Retrieve file path for error handling or logging.  Can be omitted if unused.

See how simple VBScript makes it for us to perform those date calculations?

What we’ve got here is the beginning of yet another IF statement. The latter most one is actually checking to see of the provided file should have the archive bit set by checking that its age is greater than the number of days we’ve specifiied. This is so that we don’t set the archive bit on file’s that are too young.

Now that we’ve determined that a file should have its archive bit set, we need to do the actual work of setting it. This is done very easily using the File object’s Attributes property. This Read/Write property accepts a pre-determined constant value that indicates which of the possible property settings should be set for a file. These include Read-Only, System, Hidden, and Archive among others.

At the file system level, a bit code is used to indicate these properties on a file. Likewise, the Attributes property retrieves or assigns a binary constant. VBScript allows us to use the decimal number 32 in place of its bit code equivalent (100000). Further, because we’re dealing with bit codes and binary data, we’re using And to perform a bitwise comparison rather than attempting a mathematical calculation. This is a little beyond the scope of this article so if you don’t completely understand it, just trust me that it works.

 If Not objFile.Attributes And 32 Then 'Check for existing Archive bit
 objFile.Attributes = objFile.Attributes And 32 'Set archive bit

Finally, we’ll wrap up this subroutine. First, we should check to make sure that there were no errors setting the Archive bit. This is done by making sure that our error level is still 0. Then, it’s just a matter of closing up all of our remaining IF statements and closing the subroutine.

 If Err.number <> 0 Then 'Check for error setting bit
 WScript.Echo Err.number, Err.Description, Err.Source, strFile
End If
End If
End If
Else
WScript.Echo "Incorrect date stamp in", strFile
End If
End Sub

Notice that last Else branch that corresponds to the first IF statement that we created. This branch is executed if the File’s date stamp is newer than the time of code execution. In reality, code branch should never execute, but it doesn’t hurt to check for those unexpected, albeit unheard of, possibilities.

And there you have it! A simple code sample that sets a file’s archive bit based on age in WSH. Use this code sample to improve or build your own better-formed backup schemes.

You can see and download this code sample in its entirety on the next page.

numDays = 90
strPath = "C:\Windows\Temp"
 
Set objfso = CreateObject("Scripting.FileSystemObject")
If objfso.FolderExists(strPath) Then 'Make sure folder exists
 Set objFolder = objfso.GetFolder(strPath)
Set colFiles = objFolder.Files 'Enumerate files
 If colFiles.Count > 0 Then
For Each objFile In colFiles
Call SetArchiveFlag(objFile, numDays) 'Call subroutine for each file found.
 Next
Else 'colFiles collection was empty
 WScript.Echo "No files in folder", objFolder.Path
End If
Else 'Folder path could not be found
 WScript.Echo "The specified folder", strPath, "does not exist"
End If
 
Sub SetArchiveFlag(objFile, numDays)
On Error Resume Next 'Disable automatic error handling
 Err.Clear 'Clear the StdErr object before continuing
 dateFile = objFile.DateCreated
'Can also use DateLastModified or DateLastAccessed

dateToday = Now()
If dateFile < = dateToday Then
daysOld = dateToday - dateFile 'Calculate file age
 If daysOld > numDays Then
strFile = objFile.Path 'Retrieve file path for error handling or logging. Can be omitted if unused.
 If Not objFile.Attributes And 32 Then 'Check for existing Archive bit
 objFile.Attributes = objFile.Attributes And 32 'Set archive bit
 If Err.number <> 0 Then 'Check for error setting bit
 WScript.Echo Err.number, Err.Description, Err.Source, strFile
End If
End If
End If
Else
WScript.Echo "Incorrect date stamp in", strFile
End If
End Sub

SetArchive.vbs

References   [ + ]

1. You may also notice that I never turned script debugging back on. This was not an oversight. By design, script debugging will be enabled again as soon as execution leaves the subroutine.
2. DateCreated, DateLastModified, and DateLastAccessed.

Tags

Like the read? Share it!

5 Comments

  • After posting this article, I realized one other problem spot that you may wish to check for errors. If you are running this on a large directory, or perhaps a shared one, you may want to include an IF statement that checks to make sure that a file still exists before calling the SetArchiveBit subroutine. This would prevent errors if a file were to be moved or deleted AFTER being added to the File collection but BEFORE being processed by the subroutine.

  • Excellent article – rather simple code, and very useful.

    Two suggestions though: add some sort of message when the code finish successfully, and also what about recursive code that will set archive bit on all files in folder plus all files in all sub folders? this can be pretty cool addition to the code. 🙂

  • Thanks for the comments, Shadow.

    Let me take your suggestions in order. First, I didn’t feel it necessary to include a message when the code completes, because it isn’t actually intended to run as a stand-alone script. It’s designed to be incorporated into a full backup scheme. In that case, no notification would be necessary.

    As for your second, suggestion, the original code sample for this post actually does contain recursion. I left it out of this sample for sake of brevity. However, I’ll do a follow up post to show how this can be included so stayed tuned for my next blog!

  • Fantastic entry, Nilpo. Thank you.

    The code is efficient and the way you approached the situation is rather amazing.

    Keep up posting proactively and all the best.

    Regards,
    Tony

  • @ Tony: Thank you very much. I’ll keep posting as long as readers keep sending questions! 🙂

    @ Shadow Wizard: Would you like to see archive bits on folders as well? I tend to avoid using them because I feel as though I’m forced to assume that the folder contents are all set as well….and if I don’t want to assume, I check, and that defeats the purpose of using an archive bit on a folder anyway. It also seems to me that most backup routines rely on the file level bits anyway. What’s your take?

Leave a Reply

Contact

Wanna say hello?
Drop us a line!

You'll find us here

1 Microsoft Way,
Redmond,
WA 98052, United States