Basically Visual

November/December 1998

by Peter G. Aitken (c)1998

Originally published in Visual Developer magazine


Object-Oriented File Access With the FileSystemObject Class

In the previous column, I provided a quick overview of some of Visual Basic 6's new features. In this issue we will be taking an in-depth look at one of the new toys that the Redmondians have provided - the object oriented file access and management system, called File System Objects or FSO. This isn't one of the flashier additions to the new Visual Basic, and certainly has not gotten the same attention as, say, ActiveX Data Objects or Dynamic HTML. Even so it is an important improvement to the language, one which you should start using in your own programs as soon as possible. It does not really let you do anything that cannot be done with the old file-related commands, but it makes a lot of tasks much easier. Perhaps most important is that it moves file access and manipulation from the realm of procedural programming to the modern realm of object-oriented programming with all of its advantages.

Before you panic, let me reassure you that Visual Basic's traditional file access and management statements are still supported, and I bet they will be for a long time. Even though most of you probably know those statements like the back of your hand, I recommend switching over to FSO as soon as possible. There is, however, one fly in the ointment. While the FSO framework for file management (creating folders, moving and deleting files, etc.) seems to be complete, the file access (writing data to, and reading data from, files) part of FSO is only partially complete. To be precise, you can read and write only text files - for random access and binary files you are still limited to the traditional Basic statements. I am sure this limitation will be corrected soon, but for now you have to live with it.

In this column I will introduce you to the use of the FSO system for file access and management. Some of this material is adapted from my Coriolis Group book Visual Basic 6 Programming Blue Book.

Text File Access With FSO

The top dog in the FSO hierarchy is the FileSystemObject class. To read or write a text file, you must first create an instance of this class:

Dim myFSO
Set myFSO = CreateObject("Scripting.FileSystemObject")

The following alternate syntax accomplishes the same thing:

Dim myFSO As New Scripting.FileSystemObject

Note the use of the Scripting qualifier, which identifies the library in which the FileSystemObject class is defined. You do not need to select this library, called the Microsoft Scripting Runtime, in the Visual Basic References dialog box to use the class, but doing so will give you access to the classes, methods, and properties in the Object Browser (if you are not familiar with the Object Browser, you owe it to yourself to find out - it is a great source of information about classes).

After creating an instance of the FileSystemObject class, the next step is to create a TextStream object. A TextStream object is nothing more than a regular text file enclosed in an FSO wrapper. The FileSystemObject has two methods for creating TextStream objects:

- CreateTextFile creates a new text file. If a file of the same name already exists it is overwritten.

- OpenTextFile opens a text file for reading and/or writing. If the file already exists new data is appended to existing data.

The syntax for these methods is similar. In these examples assume that myFSO is a FileSystemObject and that ts has been declared as a type Variant or Object. The first line of code creates a new file, and the second line opens an existing file.

Set ts = myFSO.CreateTextFile(filename[, overwrite[, unicode]])
Set ts = myFSO.OpenTextFile(filename[, iomode[, create[, format]]])

Filename is a string expression specifying the name, including path information, of the file. Overwrite is True or False indicating whether an existing file will be overwritten. If this argument is omitted the default is False. If overwrite is False and filename already exists, an error occurs. You can trap this error to permit the user to verify file overwrites. Unicode is True to create a Unicode file, False (the default) for an ASCII file. IOMode is set to the constants ForReading or ForAppending to read the file or write data to the end of the file. A file opened ForReading cannot be written to. Create is True or False specifying whether a new file will be created if filename does not exist. The default is False. Format is a tristate argument (one that can have three values) that determines the format of the file:

- TriStateTrue opens the file as Unicode

- TriStateFalse (the default) opens the file as ASCII

- TristateUseDefault uses the system default format setting.

Another way to obtain a TextStream object is to create a File object then use its OpenAsTextStream method. This method works with existing files only. Creating a File object is done like this, assuming that myFSO is a FileSystemObject:

Dim f
Set f = myFSO.GetFile(filename)

If the file does not already exist you can take the following approach, using CreateTextFile to create the file before creating the File object:

myFSO.CreateTextFile filename
Set f = myFSO.GetFile(filename)

After creating the File object you can invoke the OpenAsTextStream method. The syntax is:

f.OpenAsTextStream([iomode, [format]])

The iomode and format arguments are the same as described above for the CreateTextFile and OpenTextFile methods. Here's the code necessary to create a TextStream object associated with an existing file DEMO.TXT for reading using the OpenAsTextStream method:

Dim myFSO, f, ts
Set myFSO = CreateObject("Scripting.FileSystemObject")
Set f = myFSO.GetFile("demo.txt")
Set ts = f.OpenAsTextStream(ForWriting, TristateUseDefault)

Once you have created a TextStream object, you use its properties and methods to write and read text. The object's properties are listed in Table 1. In reading this table, be aware that TextStream object always has a current character position, also called the file pointer, that indicates where the next read or write operation will take place. All of these properties are read-only.

Table 1. Properties of the TextStream object.

Property Description
AtEndOfLine True if the file pointer is at the end of a line; False otherwise. This property applies only to TextStream files that are open for reading, otherwise an error occurs.
AtEndOfStream True if the file pointer is at the end of the file; False otherwise. This property applies only to TextStream files that are open for reading, otherwise an error occurs
Column Returns the column number of the file pointer. The first character on a line is at column 1.
Line Returns the current line number.

The TextStream object has a number of methods to read and write data. These methods are described in Table 2.

Table 2. Methods of the TextStream object.

Method Description
Close Closes the file associated with the TextStream object. Always execute the Close method when you are done reading/writing the file.
Read(n) Reads the next n characters from the file and returns the resulting string.
ReadAll Reads the entire file and returns the resulting string.
ReadLine Reads an entire line (up to, but not including, the newline character) from a TextStream file and returns the resulting string.
Skip(n) Skips ahead (moves the file pointer) by n characters.
SkipLine Skips to the beginning of the next line.
Write(s) Writes the string s to the file. No extra or newline characters are added.
WriteBlankLines(n) Writes n blank lines to the file.
WriteLine(s) Writes the string s to the file followed by a newline character.

You need to be aware that certain of these methods are applicable only when the file has been opened for reading or writing. If you try to use a method that is inappropriate for the file's mode, an error will occur.

File Management With FSO

As with file access, file management with FSO is based upon the FileSystemObject class. I already showed you how to create an instance of this class. Once you have done this, you use its properties and methods to perform the file manipulation tasks required by your program.

As mentioned earlier, the FileSystemObject is the "top" object in the FSO hierarchy, and as such it provides access to all of the drives (both local and network), folders, and files on the system. There are several other objects in the hierarchy, and you can see that they correspond to the way in which disk drives are organized:

- Drive object. Corresponds to a single disk drive on the system.

- Folder object. Corresponds to a single folder on a drive.

- File object. Corresponds to a single file in a folder.

Here is a brief outline of how the FSO system works. The FileSystemObject has a Drives collection which contains one Drive object for each local and network drive. You can query a Drive object's properties to obtain information about the drive, such as its type and the amount of free space on it. Each Drive object contains a single Folder object representing the drive's top-level folder, or root. Each Folder object contains a Folders collection and a Files collection. The Folders collection contains a Folder object for each subfolder within the folder, and the Files collection contains a File object for each file in the folder.

The Drives, Folders, and Files collections are like any other Visual Basic collection, and they are used the same way ( I can't explain collections here, but it's a good idea for you to understand them because they are used a lot in Visual Basic's objects).

Each Drive object has a set of properties that provides information about the physical drive. These properties are listed in Table 3. Except as noted these properties are all read-only.

Table 3. Drive object properties.

Property Description
AvailableSpace The amount of space available to a user on the specified drive or network share. Generally the same as the FreeSpace property but may differ on systems that support quotas.
DriveLetter The drive letter associated with the drive. Returns a zero-length string for network drives that have not been mapped to a drive letter.
DriveType A value indicating the type of the drive. Possible values are 0 (unknown), 1 (removable), 2 (fixed), 3 (network), 4 (CD-ROM), and 5 (RAM disk).
FileSystem The type of file system. Available return types include FAT, NTFS, and CDFS.
FreeSpace The amount of space available to a user on the specified drive or network share. Generally the same as the AvailableSpace property but may differ on systems that support quotas.
IsReady Returns True if the drive is ready, False if not. Used with removable media and CD-ROM drives, returning False if the media has not been inserted.
Path The path of the drive. This consists of the drive letter followed by a colon.
RootFolder Returns a Folder object representing the drive's root path.
SerialNumber The unique serial number identifying a disk. Use this property to verify that a removable media drive contains the proper media.
ShareName The share name assigned to a network drive. For non-network drives, a zero-length string.
TotalSize The total capacity of the drive.
VolumeName The volume name of the drive. You can write to this property to change a drive's volume name.

You can write a simple program to demonstrate how to use the Drives collection to obtain information about the drives on the system. Create a Standard EXE project and place a Text Box on the form. Set the Text Box's Multiline property to True, and set its size to nearly fill the form. Put the code from Listing 1 in the Text Box's Click event procedure. When you run the program, click on the Text Box to display a list of the system's drives and their total and free space.

Listing 1. Demonstrating the Drives collection.

Private Sub Text1_Click()

Dim fs, d, dc
Dim msg As String
Set fs = CreateObject("Scripting.FileSystemObject")
Set dc = fs.Drives
For Each d In dc
  msg = msg & "Drive " & d.Path
  If Not d.IsReady Then
    msg = msg & " is not ready." & vbCrLf
  Else
    msg = msg & vbCrLf & Space(5)
    msg = msg & "Total space: " & FormatNumber(d.TotalSize)
    msg = msg & vbCrLf & Space(5)
    msg = msg & "Free space: " & FormatNumber(d.FreeSpace)
    msg = msg & vbCrLf
  End If
Next
Text1.Text = msg

End Sub

A Folder object represents a single folder, or subdirectory, on a drive. You use the object's methods to copy, move, or delete the folder (as explained below), and the object's properties to obtain information about the folder. Perhaps most important, a Folder object contains two collections, Files and SubFolders, that provide access to the files and subfolders within the folder. Table 4 explains the properties of the Folder object.

Table 4. Properties of the Folder object

Property Description
DateCreated Date and time the folder was created.
DateLastAccessed Date and time the folder was last accessed.
DateLastModified Date and time the folder was last modified.
Drive Drive letter of the drive where the folder resides.
Files A Files collection containing all the files in the folder.
IsRootFolder True if the folder is the root, False otherwise.
Name Sets or returns the name of the folder.
ParentFolder Returns a Folder object representing the folder's parent folder.
Path The path of the folder, including drive letter.
ShortName The short name used by programs that require the old 8.3 naming convention.
ShortPath The short path used by programs that require the old 8.3 naming convention.
Size The size, in bytes, of all files and subfolders contained in the folder.
SubFolders A Folders collection containing one Folder object for each subfolder.

Because each Folder object contains information about its parent folder and its subfolders, you can easily traverse the entire folder structure on a drive. This is a powerful tool, as will be demonstrated later. First, however, let's look at the Folder object's methods.

The Copy method copies the folder and its contents to a new location. The syntax is (assuming f to be a Folder object):

f.Copy destination[, overwrite]

Destination specifies the destination where the folder is to be copied to. Set overwrite to True (the default) to overwrite existing files or folders, or to False otherwise. Note that you can also copy a folder using the FileSystemObject's CopyFolder method.

The Move method moves the folder and its contents from one location to another. The syntax is:

f.Move destination

Destination specifies the destination where the folder is to be moved to. You can also move folders with the FileSystemObject's MoveFolder method.

The Delete method deletes a folder and its contents. The syntax is:

f.Delete [force]

The optional force argument specifies whether files or folders with the read-only attribute are to be deleted (force = True) or not (force = False, the default). You can also delete folders with the FileSystemObject's DeleteFolder method.

To demonstrate the power of the Folder object I have created a small utility that counts the total number of files and folders on your C drive. This might seem like a difficult task, but as you'll see it requires relatively little code. The simplicity of this program is a result of two things: the design of the Folder object and the use of a recursive algorithm. Here's an outline of how it works:

1. Get the drive's root Folder object.

2. Use the root folder's Folders collection to access each of its subfolders.

3. Determine the number of files and subfolders in each subfolder, and add these values to the totals.

4. Use the subfolder's Folders collection to access each of its subfolders.

5. Continue until all subfolders have been processed.

Create a standard EXE project with a single form. Place one Label control and a control array of two Command Buttons on the form. Set the Label's Caption property to a blank string, and the Command Buttons' Caption properties to "Start" and "Quit." Place the following global variable declarations in the General section of the form's code:

Dim NumFolders As Long, NumFiles As Long

These variables will hold the counts of files and folders. Generally speaking it is not good programming practice to use global variables such as these, but for such a simple program it simplifies our job and will not cause any problems. Next, place the code shown in Listing 2 in the Command Button's Click event procedure. When the user clicks on the Start, this code does the following:

1. Initializes the counter variables to 0.

2. Disables the Command Buttons so they cannot be clicked while the counting process is in progress.

3. Displays "Working …" in the Label control.

4. Calls the procedure CountFilesAndFolders (yet to be written) to perform the count.

5. When execution returns from the procedure, re-enables the Command Buttons.

Listing 2. The Command Button Click event procedure.

Private Sub Command1_Click(Index As Integer)

Select Case Index
  Case 0 'Start
    NumFolders = 0
    NumFiles = 0
    Command1(0).Enabled = False
    Command1(1).Enabled = False
    Label1 = "Working ..."
    DoEvents
    Call CountFilesAndFolders
    Command1(0).Enabled = True
    Command1(1).Enabled = True
  Case 1 ' Quit
    End
End Select

End Sub

The process of counting the files and folders is performed by the procedure CountFilesAndFolders. To be more accurate, the counting process is started by this procedure. The code is shown in Listing 3. Here's what the code does:

1. Creates a FileSystemObject.

2. Loops through the Drives collection until drive C: is found.

3. Passes the drive's RootFolder, which you'll remember is a Folder object, to the procedure DoCount.

Listing 3. The CountFilesAndFolder procedure.

Public Sub CountFilesAndFolders()

Dim fs, d
Set fs = CreateObject("Scripting.FileSystemObject")
' Get drive C
For Each d In fs.Drives
  If d.DriveLetter = "C" Then Exit For
Next
Call DoCount(d.RootFolder)
Label1 = "Drive C has " & NumFiles _
  & " files in " & NumFolders _
  & " folders."

End Sub

The real work of the program is done by the DoCount procedure, shown in Listing 4. Given how much work it does, it is deceptively short. It is here that the power of the recursive algorithm comes into play. DoCount is passed a Folder object as its argument. Then, here's what it does:

1. Gets the number of subfolders in the folder and adds it to the folders count.

2. Gets the number of files in the folder and adds it to the files count.

3. Call DoCount for each subfolder in the Folders collection.

Step 3 is where the recursion occurs - when the DoCount procedure calls itself. As this program illustrates, recursion can be a powerful technique for certain tasks. If you don't believe me, try to write a program that counts the files and folders on a drive without using recursion.

Listing 4. The DoCount procedure.

Public Sub DoCount(f As Folder)

Dim f1 As Folder
NumFolders = NumFolders + f.SubFolders.Count
NumFiles = NumFiles + f.Files.Count
For Each f1 In f.SubFolders
  Call DoCount(f1)
Next
End Sub

Now let's turn our attention to the final member of the FSO team, the File object. In the FSO model, each file on a disk is represented by a File object. There are two ways to create a File object. If you know the name and path of the file, you can use the FileSystemObject’s GetFile method. Assuming that fs is an instance of the FileSystemObject class:

Dim f
Set f = fs.GetFile(filespec)

The argument filespec is the filename, including relative or absolute path. An error occurs if filespec does not exist. Note that executing GetFile does not open the file or do anything else to it, but simply returns a File object linked to the file.

Another way to obtain a File object is from a Folder object's Files collection. As you learned earlier in this chapter, you can create a Folder object for any subfolder on a disk, and the Folder object contains a Files collection containing one File object for each file in the folder. You can write code to iterate through the collection, looking for one or more files that meet a specified criterion. For example, the following code creates an array of File objects containing all the .DLL files in the application's current path.

Dim fs, f, f1, filelist()
Dim i As Integer, j As Integer
i = 0
ReDim filelist(0)
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFolder(App.Path)
For Each f1 In f.Files
  If LCase(Right(f1.Name, 3)) = "dll" Then
    i = i + 1
    ReDim Preserve filelist(i)
    Set filelist(i) = f1
  End If
Next

Once you create a File object, you have access to all the properties of the file. You also can use the object's methods for certain types of file manipulation. The methods and properties are explained in tables 5 and 6.

Table 5. The File object's methods

Method Description
Copy dest [, overwrite] Copies the file to dest. An existing file is overwritten only if overwrite is True (the default).
Move dest Moves the file to dest.
Delete [force] Deletes the files. Read-only files are deleted only if force is True. The default is False.
OpenAsTextStream Opens the file and returns a TextStream object. See Chapter 13 for details.

Table 6. The File object's properties

Property Description
Attributes Returns a value summarizing the file's attributes, as explained in detail below.
DateCreated Date and time the file was created.
DateLastAccessed Date and time the file was last accessed.
DateLastModified Date and time the file was last modified.
Drive Drive letter of the drive where the file resides.
Name Sets or returns the name of the file.
ParentFolder Returns a Folder object representing the file's parent folder.
Path The path of the file, including drive letter.
ShortName The short file name used by programs that require the old 8.3 naming convention.
ShortPath The short path used by programs that require the old 8.3 naming convention.
Size The size, in bytes, of the file.
Type Returns information about the type of the file (see below).

The Attributes property provides information about the file's attributes, such as whether it is read-only or a system file. The single value returned by the Attributes property is a composite of the individual attribute values as shown in Table 7. This table also indicates which of the individual attributes are read/write, and which are read-only.

Table 7. Component values of the Attribute property

Attribute Value Description
Normal 0 Normal file. No attributes are set.
ReadOnly 1 Read-only file. Read/write.
Hidden 2 Hidden file. Read/write.
System 4 System file. Read/write.
Volume 8 Disk drive volume label. Read-only.
Directory 16 Folder or directory. Read-only.
Archive 32 File has changed since last backup. Read/write.
Alias 64 Link or shortcut. Read-only.
Compressed 128 Compressed file. Read-only.

You use Visual Basic's logical operators to convert between individual file attributes and the value of the Attributes property.

Even though it will take a bit of getting used to, I think you'll agree with me that the FSO approach to file access and management is a great improvement over the old methods. Once FSO is expanded to include random access and binary files, you'll be able to relegate the traditional Basic file-related statements to the dustbin of history.