Implementing the CompressedFolder Class

In today’s article I’ll be continuing my explanation of the custom CompressedFolder class that we created. We’ve already discussed all of the properties and methods that have been publicly exposed through the class’s object. Now we’ll look at the rest of the methods involved, as well as how to implement the class in your own scripts.

If you need to download the code for this series again, you can find the latest version here.

Moving right along, we come to the second group of methods in this class.  These are the internal methods that are mirrored by the exposed methods.  As I mentioned previously, I like to keep my exposed methods very simple by pointing them to hidden methods that are only used internally by my class.  This provides me with a sort of interface between the class user and the code itself.

Private Sub NewCompressedFolder(strPath)

   Const adTypeBinary = 1

   Const adTypeText = 2

   Const adWriteChar = 0

   Const adSaveCreateNotExist = 1

   Const adSaveCreateOverwrite = 2

 

   With CreateObject("ADODB.Stream")

       .Open

       .Type = adTypeText

       .WriteText ChrB(&h50) & ChrB(&h4B) & ChrB(&h5) & ChrB(&h6)

       For i = 1 To 18

          .WriteText ChrB(&h0)

       Next

       .SaveToFile strPath, adSaveCreateNotExist

       .Close

       .Open

       .Type = adTypeBinary

       .LoadFromFile strPath

       .Position = 2

       arrBytes = .Read

       .Position = 0

       .SetEOS

       .Write arrBytes

       .SaveToFile strPath, adSaveCreateOverwrite

       .Close

   End With

End Sub

The exposed Open method points to the NewCompressedFolder method for creating new compressed folders.  My dedicated readers will recognize this code.  Basically, I’m exploiting the ADODB Stream object and forcing it to write a binary file.  I predetermined the binary string necessary to create a compressed file by examining an empty one with a hex editor.  If you really want to understand this portion of code, you should take the time to read my article “Compressed Folders in WSH.”

You should note that this method does not parse the provided string before attempting to use it.  This is mostly for lack of space.  If you were to implement this code or distribute it, it really should have some proper error-handling.  Sending a malformed path to the ADODB Stream object will provide some extremely strange results.

Private Function AddFile(strFolder, strFile, blnKeepOriginal)

   Set objFolder = objShell.NameSpace(strFolder)

   intCount = objFolder.Items.Count

   Select Case CBool(blnKeepOriginal)

       Case True

          objFolder.CopyHere strFile, 1548

       Case False

          objFolder.MoveHere strFile, 1548

   End Select

   Do Until objFolder.Items.Count = intCount + 1

       Sleep 200

       If Not ShellBusy Then Exit Do

   Loop

End Function

The AddFile method is used to add a file to the compressed folder.  I’ve taken advantage of the Shell Object’s ability to handle compressed folders natively by basically masking calls to the MoveHere and CopyHere methods.  Since these procedures are performed synchronously, I’ve added a little timer loop to pause execution until the process completes.  This provides some means of monitoring the process completion.  (As a part of this, I created the ShellBusy subroutine that you’ll see later.)

Here again, I’m assuming that the provided file name is correct.  In a production environment, proper handling should be included to verify that the provided parameter is correct before attempting to move or copy that file into the compressed folder.

{mospagebreak title=More internal methods}

Private Function AddFiles(strFolder, varSource, blnKeepOriginal)

   If IsArray(varSource) Then

       For Each strPath In varSource

          AddFile strFolder, strPath, blnKeepOriginal

       Next

   Else

       Set colItems = objShell.NameSpace(strSource).Items

       intCount = .Items.Count

       Select Case CBool(blnKeepOriginal)

          Case True

              objShell.NameSpace(strFolder).CopyHere colItems, 1548

          Case False

              objShell.NameSpace(strFolder).MoveHere colItems, 1548

       End Select

       Do Until objShell.NameSpace(strFolder).Items.Count = intCount + colItems.Count

          Sleep 200

          If Not ShellBusy Then Exit Do

       Loop

   End If

End Function

The AddFiles method is mirrored by the public AddMultiple method.  It is basically a rewrite of the AddFile method you just saw, with the exception that you can add multiple files at one time from either an array or an existing folder.  It functions identically to the AddFile method, except that it works recursively through the provided object.

Private Function ExtractAll(strZipFile, strFolder)

   If Not objFso.FolderExists(strFolder) Then objFso.CreateFolder(strFolder)

   intCount = objShell.NameSpace(strFolder).Items.Count

   Set colItems = objShell.NameSpace(strZipFile).Items

   objShell.NameSpace(strFolder).CopyHere colItems, 1548

   Do Until objShell.NameSpace(strFolder).Items.Count = intCount + colItems.Count

       Sleep 200

       If Not ShellBusy Then Exit Do

   Loop

End Function

Finally, the ExtractAll method is mirrored by the public Extract method.  This method is used to copy, or extract, files from an existing compressed folder.  Again, I’m basically just covering the use of the Shell Object.

{mospagebreak title=Examining the Support Methods}

The support methods I’ve created simply add functionality to my class.  They are the various functions and routines that are used in conjunction with the ones you saw on the last page.  I’ll go through each of them and explain their purpose.  You should get a clearer picture that way.

Private Function GetItemCount(strZipFile)

   GetItemCount = objShell.NameSpace(strZipFile).Items.Count

End Function

The GetItemCount function is used to populate the class object’s Count property.  Since the Count property provides a real-time count of the files in the compressed folder, I needed to create a function that would calculate that value whenever the property was referenced.  I’m simply returning to the Shell Object to get an item count on the folder. (This is a prime example of keeping my code inside of a function rather than processing it inside of the property block itself.)

Private Function ShellBusy()

   intStartSize = objFso.GetFile(FullName).Size

   Sleep 500

   ShellBusy = objFso.GetFile(FullName).Size > intStartSize

End Function

As I mentioned briefly earlier, the Shell object’s move and copy procedures are performed synchronously.  This means that the script will continue to execute while the process completes in the background.  Since there are no easily available events, it becomes difficult to monitor the completion status and success of these actions.

To circumvent these shortcomings, I’ve created a bit of code that works to pause script execution until these actions complete—essentially, turning this into an asynchronous process.  You can see this code in action in the AddFile Method.  I’m basically monitoring the starting folder count and waiting until the count changes, indicating that the file has been successfully added. 

This poses a slight problem.  In the event of a copy error, the folder count would never change, thus the script would suspend indefinitely.  So I needed a better solution.

The Shell object does not provide any means of monitoring its busy status.  It also does not provide any events to indicate when a move or copy process completes.  You could create a viable solution using WMI events (and possibly its move and copy methods, which I have not tested with compressed folders), but that would be a lot of very advanced code.

I decided to set a timeout limit on my loop so that it couldn’t cause the entire script to hang.  In the meantime, I created the ShellBusy routine that attempts to determine whether the Shell object is still busy moving files around.  It works in much the same way as my previous loop, only it uses the Shell object to monitor disk size for changes.  Since the disk size value changes more frequently than the file count (during time-consuming processes, such as when moving large files), it provided a more accurate means of watching the processes.  This method is slightly better than the first, but since neither of them is foolproof, I elected to use them together.

Private Sub Sleep(intDuration)

   ‘You must provide an applicable sleep routine for use in ASP.

   WScript.Sleep intDuration

End Sub

This simple Sleep routine is just a wrapper for a call to the WScript object’s own Sleep method.  Since the WScript object is only available in WSH (and I wanted my class to be compatible with ASP, WSC, HTA, etc.), I used a wrapper function to allow developers to drop in a compatible sleep function without having to edit any of the previous methods.

{mospagebreak title=Wrapping up}

Private Sub Class_Initialize

   Set objShell = CreateObject("Shell.Application")

   Set objFso   = CreateObject("Scripting.FileSystemObject")

End Sub

 

Private Sub Class_Terminate

   Set objShell = Nothing

   Set objFso   = Nothing

End Sub

The last two support methods are actually event handlers for the class itself.  Thus, when a class is created or destroyed, events are fired.  By capturing these events, a programmer has a place to execute code when a class is created or destroyed.  For illustration purposes I’ve used these events as a common place to create and release the global objects used by my class.

Implementing the class in your scripts is quite simple.  You’ll need to copy and paste the class into your script, and then instantiate it with the New keyword.  Here’s what the class would look like in action to create a compressed folder and add a file to it.

Set objZipFolder = New CompressedFolder

objZipFolder.Create"C:archive.zip"

objZipFolder.Add "C:myfile.txt", vbTrue

 

WScript.Echo "You have successfully added a file to " & objZipFolder.Filename

Set objZipFolder = Nothing

Now you’ve seen how to create the class, and hopefully you also have a better understanding of the code and concepts that went into its development.  There is no right or wrong way to code a particular class.  Experience will teach you the most effective practices for accomplishing a task.  I hope that you’ve gained a little insight into my own coding practices, and that you can take some of what I"ve shown here to help you create your own.  Until next time, keep coding!

[gp-comments width="770" linklove="off" ]