|
|
|
Could I get some
help working with files using FileSystemObject? |
This article has been here for a long time, but
previously, it merely pointed to
Microsoft's documentation.
I've had a few comments that I should write up my
thoughts on FileSystemObject, and what kind of
approaches I use when trying to solve problems that
involve ASP and the file system. In addition, some
comments have come by that state the Microsoft
documentation is lacking. It is certainly thorough, but
there are a few discrepancies I'd like to clear up along
the way, as well.
So, here it is. I'm going to break down a bunch of areas
where I've worked with FileSystemObject, show some
quasi-useful code samples, and hopefully point out some
potential pitfalls and how to avoid common errors and
problems.
Setup
First off, make sure you have the most recent scripting
engines, from
Microsoft Script Downloads. This is because some of
the methods and properties were added to
FileSystemObject as new releases came out, and it can
never hurt to have the most recent version anyway.
Also, make sure Norton Anti-Virus is *not* installed; if
it is there, make sure the "Script Blocking" feature is
disabled. Otherwise, any script that tries to use
FileSystemObject will just spin in your browser (see
Article #2180 for more details).
Now, before we start working on these examples, we're
going to create an include file that will help us
instantiate Scripting.FileSystemObject. We'll make a
simple call to a sub, rather than repeatedly typing in
Set fso = CreateObject("Scripting.FileSystemObject") for
every sample.
We're also going to be using the Server.MapPath()
function quite a lot, so we can stuff that into the
header as well. When passing a filename to
FileSystemObject, it expects a full local path. If you
just supply a filename, it will attempt to create the
file in the working directory for FileSystemObject,
which by default is %SYSDIR%. This will either cause
very unexpected results (especially if you manage to
overwrite an important file), or the following error
message:
Microsoft VBScript runtime
error '800a0046'
Permission denied |
So, you will see that in all of the examples, we use
this Map() function in the #include file below to turn
the filename into a local path. In this article, the
files will always be placed in the same folder as the
ASP script; however, don't feel like that has to be the
case. Just make sure that whatever folder you decide to
use is accessible (both read and write) by
IUSR_MachineName. To ensure that the Internet Guest
Account has read and write permissions to the directory,
right-click the folder name in Windows Explorer, hit
Properties, and make sure that the Security tab looks
something like this:
If the Internet Guest Account is not listed, click
Add... and find or type
<computer_name>\IUSR_<computer_name>. Then, make sure
that Read/Write are both enabled.
Whew. Now that that's all taken care of, save the
following code in a file called fsoHeader.asp:
<%
dim fso
sub startFSO()
set fso =
CreateObject("Scripting.FileSystemObject")
end sub
sub killFSO()
set fso = nothing
end sub
function Map(f)
Map = Server.MapPath(f)
end function
%> |
And then for all of the samples, assume that this line
of code is in place:
| <!--#include
file="fsoHeader.asp"--> |
In addition to saving your fingers, it will save this
article from being cluttered with the same code over and
over again. For similar reasons, I am going to avoid
using Option Explicit and 80,000 Dim statements.
If you get the following error, it probably means you
copied one of the samples, and forgot to add the above
<!--#include--> directive:
Microsoft VBScript runtime
error '800a000d'
Type mismatch: 'startFSO' |
Please be sure to add the #include line to the beginning
of any of the samples. I'm not going to include it in
all samples, in an effort to keep the code clear and
concise.
Back to top
Creating a file
To create a text file, you use the CreateTextFile()
method, which returns a TextStream object. This method
takes a required parameter filename, and two optional
parameters: overwrite and unicode. The optional
parameter overwrite dictates how FileSystemObject should
behave if the file already exists. I'm going to leave
unicode parameters for a more advanced tutorial;
typically, you will only be dealing with ASCII files
when using the FileSystemObject, and luckily for the
complexity of our examples, ASCII is the default.
Here is an example where we just pass the filename, and
write one line of text to the file using the writeline()
method.
<%
startFSO()
filepath = Map("foo.txt")
set fs = fso.CreateTextFile(filepath)
fs.writeline("This is a test.")
fs.close: set fs = nothing
%> |
Now, this example is supposed to fail if the file
already exists (according to the
CreateTextFile() method documentaion in the MSDN
Library). However, it doesn't behave correctly, at least
in version 5.6 of the scripting engine (scrrun.dll, the
DLL used by FileSystemObject, has a default of TRUE for
the overwrite parameter). Change the text in the
WriteLine() call, and run the script again. You'll
notice that the file was re-generated, and contains only
the new line of text you entered. At the time of writing
of this article (December 2003), the documentation
hadn't been corrected to reflect the behavior, but
Microsoft has been made aware of the issue.
In order to prevent being burned in the future by
differences in documented and actual behavior of
optional parameters, you might be better off being
explicit than relying on the documentation.
Let's make sure that overwrite is enabled this time, and
run the script again.
<%
startFSO()
filepath = Map("foo.txt")
set fs = fso.CreateTextFile(filepath,
true)
fs.writeline("This is another test.")
fs.close: set fs = nothing
killFSO()
%> |
Now, let's change the optional overwrite parameter to
false. You may want to do this in the situation that you
want to create the file *only* if it exists; otherwise,
return an error. If we use the same filename:
<%
startFSO()
filepath = Map("foo.txt")
'set fs = fso.CreateTextFile(filepath,
false)
'fs.writeline("This is a test.")
'fs.close: set fs = nothing
killFSO()
%> |
We should see this error:
Microsoft VBScript runtime
error '800a003a'
File already exists |
There are at least three ways around this ugly error. We
can use on error resume next:
<%
startFSO()
filepath = Map("foo.txt")
on error resume next
set fs = fso.CreateTextFile(filepath,
false)
if err.number <> 0 then
response.write "File exists."
else
fs.writeline("This is a test.")
fs.close: set fs = nothing
end if
killFSO()
%> |
We can use the FileExists() method first:
<%
startFSO()
filepath = Map("foo.txt")
if fso.FileExists(filepath) then
response.write "File exists."
else
set fs =
fso.CreateTextFile(filepath, false)
fs.writeline("This is a test.")
fs.close: set fs = nothing
end if
killFSO()
%> |
Or we can use the OpenTextFile() method, which will be
defined in the next section.
Back to top
Appending to a file
Adding data to the end of a text file is very much like
creating a text file and using WriteLine(). We use the
OpenTextFile() method for this; like CreateTextFile(),
there is a required parameter, filename. However, there
are some new parameters, namely iomode (which tells the
object to prepare the file for reading, writing, or
appending), and create (which tells the object whether
or not to create the file, in the case that it doesn't
already exist). So that we don't have to memorize the
reading, writing and appending constants for the iomode
parameter, we can put them into our fsoHeader.asp file.
(I'm not sure why VBScript lacks the feature of
recognizing this type of constant the way VB can.) So
add this to the bottom of the header file:
<%
' ...
CONST ForReading = 1
CONST ForWriting = 2
CONST ForAppending = 8
%> |
And again, there is a unicode parameter that we will be
ignoring, since we will only be working with ASCII
files. And, unlike overwrite, we can be fairly certain
that Microsoft isn't going to change the default
behavior for text file format (at least not before
Longhorn <G>).
So, here is a quick sample that shows how to open a
file, and write a line to it:
<%
startFSO()
filename = Map("blat.txt")
set fs = fso.OpenTextFile(filename,
ForWriting)
fs.writeline("Line 1")
fs.close: set fs = nothing
killFSO()
%> |
Note that if the file doesn't exist, you will receive
the following error:
Microsoft VBScript runtime
error '800a0035'
File not found |
To make sure the file exists, you can use the
FileExists() method as described above, or — if it's all
right to completely overwrite the file if it does exist
— add the create parameter.
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForWriting, true)
fs.writeline("Line 1")
fs.close: set fs = nothing
killFSO()
%> |
If you want to create the new file when it doesn't
already exist, or merely append to the existing file if
it's already there, you can set iomode to ForAppending,
and the create parameter to true. Here is an example:
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForAppending, true)
fs.writeline("Line n?")
fs.close: set fs = nothing
killFSO()
%> |
When you view foo.txt, you will see "Line n?" and, if
the file already existed prior to running the above
code, you will see that this is the last line in the
file, and follows any of the previous contents.
And here is a sample that creates a new text file,
writes a line to it, and closes it; then opens the file
and appends a second line to it:
<%
startFSO()
filename = Map("foo2.txt")
set fs = fso.OpenTextFile(filename,
ForWriting, true)
fs.writeline("Line 1.")
fs.close: set fs = nothing
set fs = fso.OpenTextFile(filename,
ForAppending)
fs.writeline("Line 2.")
fs.close: set fs = nothing
killFSO()
%> |
You can use the write() method instead of the
writeline() method, if you want to use multiple
statements to append to only one line. This example
should demonstrate (as well as show how the existing
foo.txt is overwritten by the iomode of ForWriting):
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForWriting, true)
fs.write("Line 1.")
fs.write(" Still line 1.")
fs.close: set fs = nothing
set fs = fso.OpenTextFile(filename,
ForAppending)
fs.write(" What? Still line 1?")
fs.close: set fs = nothing
killFSO()
%> |
And if you want to use a single statement to write
multiple lines, you can use fs.writeline and the
constant for carriage return / line feed pair, VBCrLf.
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForWriting, true)
fs.write("Line 1." & VBCrLf & "Line
2.")
fs.write(" Still line 2.")
fs.close: set fs = nothing
killFSO()
%> |
(You might find that using write() actually writes to
the next line... if you use writeline() in the above
sample, you can reproduce: there will be a trailing
VBCrLf, and that causes the writing to start on the next
line down.)
Back to top
Reading a file
To read a file's contents, you can simply use the
OpenTextFile() method, with an iomode of ForReading. You
can return the entire content of the file into a
variable, or write it out to the page directly, using
the ReadAll() method. For example:
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
Response.Write(fs.ReadAll())
fs.close: set fs = nothing
killFSO()
%> |
Now of course, since the file is plain text, carriage
returns are not going to be visible in HTML. One way to
avoid this is by using <PRE></PRE>:
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
Response.Write("<PRE>" & fs.ReadAll() &
"</PRE>")
fs.close: set fs = nothing
killFSO()
%> |
However, formatting leaves a lot to be desired. So, we
need to replace the ASCII carriage returns/line feeds
with HTML line feeds (e.g. "<br>"). I usually add a
non-breaking space(" ") to each replacement, since some
versions of Internet Explorer will ignore successive
<br><br> tags, and multiple carriage returns might have
been intentional.
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
Response.Write(Replace(fs.ReadAll(),
VBCrLf, " <br>"))
fs.close: set fs = nothing
killFSO()
%> |
Depending on the source of the file, you may have to
experiment with replacing only CHR(13) or only CHR(10)
rather than VBCrLf, because different editors and
operating systems will use different combinations of
CR/LF to enforce a carriage return. Another way would be
to read each line individually, using the ReadLine()
method and the AtEndOfStream property:
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
Do Until fs.AtEndOfStream
Response.Write(fs.ReadLine() &
" <br>")
Loop
fs.close: set fs = nothing
killFSO()
%> |
However, this is a cumbersome and inefficient way to
process the file (since you have to maintain an open
handle to the file throughout both the read and the
write operations), and I usually recommend against using
the AtEndOfStream proeprty at all — deal with the
contents as a whole using ReadAll(). Usually, if I am
not in control of how the files are created, I will use
CHR(10) as my delimiter:
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
Response.Write(Replace(fs.ReadAll(),
Chr(10), " <br>"))
fs.close: set fs = nothing
killFSO()
%> |
If you want an approximate line count in a file, you can
use the split() method, like this:
<%
startFSO()
filename = Map("foo.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
lines = split(fs.ReadAll(), Chr(10))
' need to add 1, because split() creates
a 0-based array
lineCount = UBound(lines) + 1
Response.Write "Approximately " &
lineCount & " line(s)."
fs.close: set fs = nothing
killFSO()
%> |
I don't think there's a very efficient way to do this
without actually retrieving the contents of the file. If
the file doesn't exist, you will get the following
error:
Microsoft VBScript runtime
error '800a003e'
Input past end of file |
So, you might want to use the FileExists() methodology
discussed previously in order to avoid potential errors
if the filename is invalid. Note that if the file exists
but contains *no* carriage returns (e.g. a one-liner),
the lineCount will return 0. Similarly, if the last line
of the file does not end with a carriage return, the
count will be one short.
There may be cases where you want to retrieve a specific
line in a file. For example, the second line, or the
third last line, or the middle line. You can use a
similar approach to getting the lineCount, except that
we can locate a specific line's contents by referencing
the nth element in the array — remembering that split()
returns a 0-based array. Say we have a file, foobar.txt,
that looks like this:
blat
splunge
bar
yoohoo
splunge 2
aaron |
The following example will show how to get different
lines:
<%
startFSO()
filename = Map("foobar.txt")
set fs = fso.OpenTextFile(filename,
ForReading, true)
lines = split(fs.ReadAll(), Chr(10))
' the second line:
Response.Write "2nd line = " & lines(1)
& "<p>"
' the third-last line:
Response.Write "3rd last line = " &
lines(ubound(lines)-2) & "<p>"
' the middle line:
Response.Write "Middle line = " &
lines(ubound(lines)\2) & "<p>"
fs.close: set fs = nothing
killFSO()
%> |
A few notes about this solution. One is that the
"middle" line will round down, if there are is an even
number of lines. Another is that you should probably
check what the ubound is before attempting to perform
math on it; otherwise, you could end up with something
like this:
Microsoft VBScript runtime
error '800a0009'
Subscript out of range: '[number: -1]' |
What if you want to find all the lines containing a
certain word? For example, with the file above
(foobar.txt), I want to return all the lines that
contain the word "splunge" (ignoring the fact that
splunge isn't really a word). Well, you can do something
like this:
<%
startFSO()
filename = Map("foobar.txt")
wordToFind = "splunge"
set fs = fso.OpenTextFile(filename,
ForReading, true)
lines = split(fs.ReadAll(), Chr(10))
for i = 0 to ubound(lines)
line = lines(i)
wordToFind = lcase(wordToFind)
if instr(lcase(line), wordToFind) >
0 then
Response.Write "Line " & i+1 &
":" & _
line & "<br>"
end if
next
fs.close: set fs = nothing
killFSO()
%> |
If you want to read the properties of a file, such as
the size, date modified, or dimensions (e.g. of a JPG
file), see
Article #2296.
Back to top
Modifying files
Continuing with the above example, let's say I wanted to
modify the file, removing the lines that contain the
word "splunge." We can use split() to build a new set of
content after a read, then open the file for writing and
dump the new contents to it.
<%
startFSO()
filename = Map("foobar.txt")
wordToFind = "splunge"
set fs = fso.OpenTextFile(filename,
ForReading, true)
lines = split(fs.ReadAll(), VBCrLf)
for i = 0 to ubound(lines)
wordToFind = lcase(wordToFind)
if instr(lcase(lines(i)),
wordToFind) > 0 then
lines(i) = ""
else
lines(i) = lines(i) & VBCrLf
end if
newContent = newContent & lines(i)
next
fs.close: set fs = nothing
set fs = fso.OpenTextFile(filename,
ForWriting, true)
fs.writeline(newContent)
fs.close: set fs = nothing
killFSO()
%> |
What if you want to merge the contents of two files into
one? Imagine a file foo_a.txt that contains the
following:
And foo_z.txt contains the following:
Desired results would be foo_az.txt, with the following
contents:
The following code will accomplish that:
<%
startFSO()
fileA = Map("foo_a.txt")
fileZ = Map("foo_z.txt")
fileAZ = Map("foo_az.txt")
set fs = fso.OpenTextFile(fileA,
ForReading, true)
contentA = fs.ReadAll()
fs.close: set fs = nothing
set fs = fso.OpenTextFile(fileZ,
ForReading, true)
contentZ = fs.ReadAll()
fs.close: set fs = nothing
contentAZ = contentA & VBCrLf &
contentZ
set fs = fso.CreateTextFile(fileAZ,
true)
fs.writeline(contentAZ)
fs.close: set fs = nothing
killFSO()
%> |
And the following code will split the contents of one
file into two files, by dividing the lines in half. You
could use other criteria for deciding which file's
contents to append to within the loop, of course.
<%
startFSO()
fileA = Map("foo_a.txt")
fileZ = Map("foo_z.txt")
fileAZ = Map("foo_az.txt")
set fs = fso.OpenTextFile(fileAZ,
ForReading, true)
contentAZ = fs.ReadAll()
fs.close: set fs = nothing
lines = split(contentAZ, VBCrLf)
middle = (ubound(lines)\2)
for i = 0 to ubound(lines)
line = lines(i) & VBCrLf
if i < middle then
contentA = contentA & line
else
contentZ = contentZ & line
end if
next
set fs = fso.CreateTextFile(fileA,
true)
fs.write(contentA)
fs.close: set fs = nothing
set fs = fso.CreateTextFile(fileZ,
true)
fs.write(contentZ)
fs.close: set fs = nothing
killFSO()
%> |
Back to top
Renaming a file
FileSystemObject does not have a rename method, for some
reason. There are two workarounds. One is to simply
"move" the file using the MoveFile method, giving the
destination a new name:
<%
startFSO()
OldName = "foo_a.txt"
NewName = "aFoo.txt"
fso.moveFile Map(OldName), Map(NewName)
killFSO()
%> |
Another method is to actually obtain a file handle using
GetFile(), and modify its read/write property "name":
<%
startFSO()
OldName = "foo_a.txt"
NewName = "aFoo.txt"
set file = fso.GetFile(Map(OldName))
file.name = NewName
set file = nothing
killFSO()
%> |
In either case, if a file already exists with the new
filename, you will get the following error:
Microsoft VBScript runtime
error '800a003a'
File already exists |
So, similar to earlier examples, to get around this you
could prevent this error by testing for the file's
existence using the FileExists() method.
Back to top
Moving a file
Another obvious use for the MoveFile() method is to,
well, move a file. So here is a way we could move a file
into a subfolder of the current folder.
<%
startFSO()
FileName = "foo_a_copy.txt"
fso.MoveFile Map(FileName),
Map("subfolder/" & filename)
killFSO()
%> |
If the destination file already exists, you will get
this error:
Microsoft VBScript runtime
error '800a003a'
File already exists |
Again, like the other examples, you can avoid this error
by using the FileExists() method or on error resume
next.
Back to top
Copying a file
Another common task is to copy a file (or a set of
files), for example to create a backup. Let's say that
foo_a.txt is a very critical file and we like to back it
up whenever we are playing with it, for example using it
for FileSystemObject tutorials. And let's say we'd like
to make a copy of it, in its current state, and place it
in the backup/ subfolder. Well, we could do this:
<%
startFSO()
FileName = "foo_a.txt"
fso.CopyFile Map(FileName),
Map("backup/" & filename)
killFSO()
%> |
Note that the CopyFile() method does not care if the
file exists in the destination folder; it will overwrite
it by default. To avoid overwriting an existing file,
you could use the same FileExists() method we've talked
about numerous times, or you could set the third
parameter (overwrite) to false:
<%
startFSO()
FileName = "foo_a.txt"
fso.CopyFile Map(FileName),
Map("backup/" & filename), false
killFSO()
%> |
If you do this, and the file already exists, you will
get the following error:
Microsoft VBScript runtime
error '800a003a'
File already exists |
(Which you could trap with on error resume next.)
Back to top
Copying a set of files
This example will demonstrate a few additional
techniques of FileSystemObject and the CopyFile()
method. Let's say you have a folder with a bunch of TXT
files and a bunch of GIF files, and you only want to
make a backup of the GIF files. You can use iterate
through the files collection, and test each extension
using the GetExtensionName() method:
<%
startFSO()
srcFolder = Map("/fso/") & "\"
dstFolder = Map("backup/") & "\"
Set folder = fso.GetFolder(srcFolder)
For Each File In folder.Files
Set fs =
fso.GetFile(Map(File.Name))
If fso.GetExtensionName(fs) = "gif"
Then
fso.CopyFile Map(File.Name),
dstFolder
End If
Set fs = Nothing
Next
killFSO()
%> |
A much easier way would be to use wildcard matching,
which is acceptable for the *source* of a CopyFile()
call.
<%
startFSO()
srcFolder = Map("/fso/") & "\"
dstFolder = Map("backup/") & "\"
fso.CopyFile srcFolder & "*.gif",
dstFolder
killFSO()
%> |
Note that wildcard characters can also be used when
using the MoveFile() method, however they are only
allowed in the *file* element of the path. In other
words, you can't delete all test.txt files in all
folders matching some pattern.
Back to top
Deleting a file
FileSystemObject has another convenient method:
DeleteFile(). You can use this method to delete
individual files (or, as with MoveFile(0 and CopyFile(),
sets of files using a wildcard). Here is an example of
how we would delete foo_a.txt:
<%
startFSO()
FileName = Map("foo_a.txt")
fso.DeleteFile FileName
killFSO()
%> |
Note that this will not work if foo_a.txt is currently
set to read only. You will get
the following error:
Microsoft VBScript runtime
error '800a0046'
Permission denied |
(You will also see this error if IUSR does not have
write/modify permissions on the folder.)
Assuming permissions are adequate, and that the problem
really is that the file has been erroneously marked as
read-only, there are several ways to get around this.
One is to use the force parameter of the DeleteFile()
method:
<%
startFSO()
FileName = Map("foo_a.txt")
fso.DeleteFile FileName, true
killFSO()
%> |
Another is to test the Attributes property to see if the
file is read-only, and if it is, switch that bit using
XOR:
<%
startFSO()
FileName = Map("foo_a.txt")
Set file = fso.GetFile(FileName)
If CBool(file.Attributes XOr 1) Then
file.Attributes = file.Attributes
XOr 1
End If
fso.DeleteFile FileName
' or File.Delete
killFSO()
%> |
(Note that since we already have a valid handle to the
File object, we could have opted to use its Delete()
method rather than FSO's DeleteFile() method. The choice
here is arbitrary; both methods work the same way.)
Here are the settings of the Attributes property:
|
Normal |
0 |
Normal file. No attributes are set. |
|
ReadOnly |
1 |
Read-only file. Attribute is read/write. |
|
Hidden |
2 |
Hidden file. Attribute is read/write. |
|
System |
4 |
System file. Attribute is read/write. |
|
Volume |
8 |
Disk drive volume label. Attribute is
read-only. |
|
Directory |
16 |
Folder or directory. Attribute is read-only. |
|
Archive |
32 |
File has changed since last backup.
Attribute is read/write. |
|
Alias |
1024 |
Link or shortcut. Attribute is read-only. |
|
Compressed |
2048 |
Compressed file. Attribute is read-only. |
Links to official documentation
Properties
Methods
Objects
Collections
Back to top
|
|
|
|
|