This month, I continue my discussion of the File System Object (FSO) model by looking at the TextStream object. I show you how to access this object so that you can read and write to text files. However, before I discuss the TextStream object, I want to discuss why you might encounter a security warning if you run VBScript code that uses objects in the FSO model.
Security Implications When Using VBScript and FSO
Microsoft initially designed VBScript as a tool to make Web pages more interactive and responsive. As such, VBScript was subject to several security restrictions, including an inability to access the local file system. However, VBScript quickly evolved from a mere tool for writing Web pages to a general-purpose instrument for automating COM-based software.
Microsoft then introduced a modernized version of command.com for Windows OSs called Windows Script Host (WSH). Because VBScript is one of the languages that WSH supports, VBScript needed to gain access to local file-system elements so that VBScript code executing within the Windows shell could read and write to local files as well as access memory, databases, and other resources.
In response to the need for file-system access, Microsoft developed the FSO model. This model gives scriptwriters the tools they need to manipulate file-system elements in VBScript code. However, if you try to execute VBScript code with FileSystemObject objects from within a Web page, you'll encounter a security warning because of VBScript's roots. You can only execute VBScript code with FileSystemObject objects from within the WSH environment (i.e., a desktop script). Accessing the file system from a desktop script is considered safe because scriptwriters are supposed to be fully conscious of a script's behavior before they run it.
Accessing the TextStream Object
As the name suggests, TextStream is an object that represents a stream of text from a text file in a file system. In other words, the TextStream object is the logical representation of the contents of a text file. (The FSO model doesn't support binary files. For more information, see the Web-exclusive sidebar "Why Not Binary Files?" on the Win32 Scripting Journal Web site at http://www.win32scripting.com.) To access a TextStream object, you can use the FileSystemObject object's CreateTextFile or OpenTextFile method or the File object's OpenAsTextStream method.
Using the CreateTextFile Method
The FileSystemObject object's CreateTextFile method creates a new text file with the filename you specify. Its syntax is
fsoObject.CreateTextFile _ (filename _ \[, overwrite\[, unicode\]\])
where fsoObject is a FileSystemObject (or Folder) object and filename is the mandatory argument that specifies the name of the file you want to create. For example, the code
Set fso = CreateObject _ ("Scripting.FileSystemObject") Set ts = fso.CreateTextFile _ ("foo.txt")
creates a text file named foo.txt. CreateTextFile has two optional arguments: overwrite and unicode. By default, CreateTextFile creates ASCII files but doesn't overwrite existing files. By setting the overwrite argument to True, you're telling CreateTextFile to overwrite an existing file. By setting the unicode argument to True, you're telling CreateTextFile to create a Unicode file.
Using the OpenTextFile Method
The OpenTextFile method of the FileSystemObject object opens an existing file in text mode. Its syntax is
fsoObject.OpenTextFile(filename _ \[, iomode\[, create\[, _ format\]\]\])
where iomode, create, and format are optional arguments.
The iomode argument. You use the iomode argument to set one of three I/O modes:
- ForReading (value of 1). Opens a file for reading only (i.e., doesn't allow writing). This mode is the default setting.
- ForWriting (value of 2). Opens a file, and overwrites its content (i.e., doesn't allow reading).
- ForAppending (value of 8). Opens a file, and writes to the end of it (i.e., doesn't allow reading).
If you use ForReading, you can read forward only. You can skip lines, but you can't move the file pointer backward. ForWriting behaves in an even more restrictive way: It immediately truncates the file and overwrites its content. Thus, the seemingly harmless code
Set ts = fso.OpenTextFile _ ("foo.txt", ForWriting)
wipes out any existing content in the file foo.txt. Using OpenTextFile with the ForWriting I/O mode is equivalent to using CreateTextFile with the overwrite option. Like ForWriting, ForAppending lets you write to a file but with a significant difference: ForAppending preserves the file's existing content. ForAppending opens the file, moves the file pointer to the end, and adds the new text to the existing text.
As you can see, the TextStream object is useful but limited. You can add new text but only at the beginning of a file (if you overwrite the entire file) and at the end of a file. None of the I/O modes let you insert text in the middle of a text stream or let you overwrite only a portion of a file. In addition, you can't specify more than one iomode in the same TextStream instance. For example, you can't use the code
Set ts = fso.OpenTextFile _ ("foo.txt", ForReading _ Or ForWriting)
The create argument. You use the create argument to create a file on the fly if the specified file doesn't exist. By default, an error occurs if OpenTextFile doesn't find the file that the filename argument specifies. However, if you set the create argument to True, OpenTextFile creates the specified file rather than generates an error.
If you want to avoid a nonexistent-file error but don't want to create a file, you can use the code
If Not fso.FileExists _ ("foo.txt ") Then WScript.Quit End If Set ts = fso.OpenTextFile ("foo.txt")
In this code, the If...Then...Else statement tells the script to quit if the specified file doesn't exist.
The format argument. You use the format argument to specify the character format. You can set one of three formats:
- TristateUseDefault (value of 2), which opens the file using your system's default format
- TristateTrue (value of 1), which opens the file in Unicode format
- TristateFalse (value of 0), which opens the file in ASCII format
ASCII is the default setting, so if you want to use this format, you don't need to include the format argument in your code.
The OpenAsTextStream Method
The File object's OpenAsTextStream method opens an existing file. Its syntax is
fileObject.OpenAsTextStream _ (\[iomode, \[format\]\])
where fileObject is the File object representing the file that you want to open. Thus, you need to access both the FileSystemObject object and the File object before you use this method with code such as
Set fso = CreateObject _ ("Scripting.FileSystemObject") Set f = fso.GetFile("foo.txt") Set ts = f.OpenAsTextStream()
OpenAsTextStream offers the optional iomode and format arguments.
Accessing the TextStream Object's Properties and Methods
After you access the TextStream object, you can use its properties and methods. The TextStream object lets you work with only text files and not binary files; therefore, when you work with properties and methods, you need to think in terms of lines of text and number of characters rather than in terms of chunks of data of a given size.
As Table 1 shows, you can use four properties. To access these read-only properties, you use the syntax
where tsObject is the name of the TextStream object and property is the name of the property you want to access.
The AtEndOfLine and AtEndOfStream properties tell you whether the file pointer is at the end of a line or text stream, respectively. These properties return a value of True if the file pointers are at the end and False if they're not. AtEndOfLine is useful for checking the line boundary when you're skipping blocks of characters.
The Column and Line properties tell you about the file pointer's current position. The Column property returns the column number; the Line property returns the line number.
As Table 2 shows, you can access nine methods. These methods let you read from, write to, navigate through, and close files.
Reading from Files
To read a file's contents, you can use the Read, ReadLine, and ReadAll methods. The Read method fills a string with the number of characters you specify. Its syntax is
where characters is the number of characters you want to read.
The ReadLine method fills a string with the content of the current line by reading the text from the file pointer's current position up to, but not including, the next new line. Thus, ReadLine reads up to the new-line character, but the string it returns doesn't include that character. Because ReadLine reads the entire line and not just several characters from it, its syntax doesn't include the characters argument:
ReadLine is useful for reading all the lines in a file into an array's indexes, as the code in Listing 1 shows. This code begins by declaring the ForReading constant and two variables: arrText (an uninitialized dynamic array) and iUpperBound (an integer used later to initialize the array). The code then creates an instance of the FileSystemObject object and uses that object's OpenTextFile method to open the existing file named foo.txt. The code sets the optional iomode argument to ForReading, which means that you can only read from foo.txt.
After setting iUpperBound to 0, the code uses a While...Wend statement to loop through each line in foo.txt. At each line, the code initializes the array, reads the line into an array index, and then sets a new upper bound and index. The code closes the file when it reaches the end of the text stream. (If you're unfamiliar with VBScript arrays and the While...Wend statement, see my July 1999 and September 1999 columns, respectively.)
This example further demonstrates that the TextStream object is useful but limited. The Read and ReadLine methods' inability to set the file pointer at a specific position within the text hampers their usefulness because you can't move backward in a file. A partial workaround for this inability is the ReadAll method, which has the syntax
This method reads all the lines at once and returns them in one string. ReadAll is efficient because it performs the reading with just one access. For example, the code
Set ts = fso.OpenTextFile _ ("foo.txt", ForReading) buf = ts.ReadAll
puts all the text in foo.txt into one string that is stored in the memory buffer. In the memory buffer, you can easily navigate forward and backward along the string. You can also use string-manipulation functions to extract or change the portions of the string. (See my November 1999 column for information about VBScript's string-manipulation functions.) When you're finished manipulating the string, you can save the changes by writing the string to a file.
Writing to Files
To write to a file, you use the Write method. Its syntax is
where string is the text you want to write to the file. For example, if you want to write the string that you manipulated in the memory buffer to foo.txt, you use the code
Set ts = fso.OpenTextFile _ ("foo.txt", ForWriting) ts.Write buf
This code opens foo.txt in the ForWriting I/O mode, then uses the Write method to write the string to the file.
When you use the Write method, the TextStream object writes only the string you pass to it. The object doesn't add any new-line characters or white space (i.e., blank lines). As a result, if you want to write different strings on different lines, you need to add a new-line character with code such as
ts.Write buf & vbCrLf
This code concatenates a line return (vbCrLf) to the end of the string.
Another approach is to use the WriteLine method. WriteLine writes the specified string, then adds a new-line character so that the next writing begins on the next line. However, as the following syntax shows, including a string is optional:
If you use WriteLine without the string argument with code such as
you simply receive a new blank line.
If you want to write several consecutive blank lines, you can use the WriteBlankLines method. Its syntax is
where lines is the number of blank lines that you want to add. For example, the code
writes five consecutive new-line characters to the file. When you want to add more than one blank line in a row, using WriteBlankLines is more efficient than using repeated calls to WriteLine because the TextStream object has to process fewer calls.
Navigating Through and Closing Files
To navigate forward in a file, you can use the Skip and SkipLine methods. SkipLine lets you jump to the beginning of the next line. To skip only a few characters, you use Skip instead. The syntax for these methods is
where characters is the number of characters you want to skip. When you're finished working with a file, you use the Close method to close it. This method's syntax is
Using NTFS Streams
To complete this discussion of the TextStream object, let me turn your attention to a special feature that you can use for files under the NTFS platform. By NTFS platform, I simply mean a Windows 2000 (Win2K) or Windows NT disk volume formatted as NTFS (as opposed to FAT and FAT32 options available in Windows 98). Under an NTFS disk partition, files can have more than one data stream. A file's content is typically the main unnamed stream. You can create additional named streams within a file.
To add a named data stream, you use the CreateTextFile method with the special syntax
CreateTextFile _ ("filename:streamname")
where filename:streamname is the name you're giving the file followed by a colon and the name you're giving the data stream. (Because the colon isn't a valid character for long filenames, the system can't inadvertently produce an error when it processes a filename with a colon inside.) For example, to create a data stream called MyExtraInfo within a file named foo.txt, you use the code
Set fso = CreateObject _ ("Scripting.FileSystemObject") Set ts = fso.CreateTextFile _ ("foo.txt:MyExtraInfo")
After you create a named data stream, you can consider it as a file within a file. Hence, you can use any of the TextStream object's properties or methods to manipulate it. For example, if you want to create a data stream called MyExtraInfo and then write text to that stream, you use the code
Set fso = CreateObject _ ("Scripting.FileSystemObject") Set ts = fso.CreateTextFile _ ("foo.txt:MyExtraInfo") ts.Write "This is my extra info" ts.Close
To open an existing stream in a text file, you use the OpenTextFile method with filename:streamname as the argument. For example, the code
Set fso = CreateObject _ ("Scripting.FileSystemObject") Set ts = fso.OpenTextFile _ ("foo.txt:MyExtraInfo") buf = ts.ReadAll() ts.Close
opens the MyExtraInfo stream and reads it into the buf variable. You can also create a File object through the GetFile method, then use OpenAsTextStream to access the content.
Using named streams is a good way to hide special or custom information within a file. This information isn't visible through the shell, unless you use special shell extensions. However, if you move the file from an NTFS partition to a volume with a different file system (including disk and CD-ROM drives), this extra information will disappear.