Files and folders: FileSystemObject and TextStream
Test and create paths, read and write text files, walk directories, and import CSV without corrupting the data.
FileSystemObject
Option Explicit
Private Function FSO() As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
End Function
Sub Paths()
Dim f As Object
Set f = FSO()
Dim p As String
p = f.BuildPath(ThisWorkbook.Path, "data") ' correct separator per OS
Debug.Print p
Debug.Print f.GetParentFolderName(p)
Debug.Print f.GetBaseName(p) ' data
Debug.Print f.GetExtensionName("report.csv") ' csv
Debug.Print f.GetAbsolutePathName("..\logs")
If Not f.FolderExists(p) Then f.CreateFolder p
Debug.Print f.FileExists(f.BuildPath(p, "in.csv"))
Debug.Print f.GetTempName()
End Sub
Sub ReadText()
Dim f As Object, ts As Object
Set f = FSO()
Dim path As String
path = f.BuildPath(ThisWorkbook.Path, "data\in.csv")
If Not f.FileExists(path) Then Exit Sub
Set ts = f.OpenTextFile(path, 1, False, -2) ' 1 = ForReading, -2 = system default
Dim line As String
Dim n As Long
Do Until ts.AtEndOfStream
line = ts.ReadLine
If Len(line) > 0 Then n = n + 1
Loop
ts.Close
Debug.Print n & " non-empty lines"
End Sub
Sub WriteText()
Dim f As Object, ts As Object
Set f = FSO()
Dim path As String
path = f.BuildPath(ThisWorkbook.Path, "data\out.txt")
Set ts = f.CreateTextFile(path, True, False) ' overwrite, ANSI
ts.WriteLine "header"
ts.WriteLine "row 1"
ts.Close
' append to the same file
Set ts = f.OpenTextFile(path, 8, True, -2) ' 8 = ForAppending
ts.WriteLine "row 2"
ts.Close
End Sub| Mode | Value | Effect |
|---|---|---|
ForReading | 1 | Fails if the file does not exist |
ForWriting | 2 | Truncates an existing file |
ForAppending | 8 | Adds to the end, no newline inserted |
CreateTextFile | n/a | Creates or overwrites, second argument decides |
| Format TristateUseDefault | -2 | System ANSI |
| Format TristateTrue | -1 | Unicode UTF-16 |
| Format TristateFalse | 0 | ASCII |
CreateObjectreturns late-bound objects: no IntelliSense, and every member call is resolved at run time, which is slower than early binding for a loop that reads a million lines.- Always close a
TextStreamin a cleanup path. An unclosed handle keeps the file locked, and the next run fails with a permission error that looks unrelated. TristateUseDefaultwrites the system ANSI codepage, which breaks non-ASCII characters on another machine. Write UTF-8 explicitly when the consumer expects it.- There is no UTF-8 option in
FileSystemObject. For UTF-8 output, useADODB.StreamwithCharset = "utf-8", or a shell call.
Walking a directory
Function ListFiles(ByVal folderPath As String, ByVal pattern As String) As Collection
Dim f As Object
Set f = CreateObject("Scripting.FileSystemObject")
Dim out As Collection
Set out = New Collection
If Not f.FolderExists(folderPath) Then
Set ListFiles = out
Exit Function
End If
Dim folder As Object, file As Object, sub_ As Object
' files in this folder only: * means all files
For Each file In f.GetFolder(folderPath).Files
If LCase$(Right$(file.Name, Len(pattern) + 1)) = "." & pattern Then
out.Add file.Path
End If
Next file
' recurse into subfolders when you need the whole tree
For Each sub_ In f.GetFolder(folderPath).SubFolders
Dim nested As Collection
Set nested = ListFiles(sub_.Path, pattern)
Dim v As Variant
For Each v In nested
out.Add v
Next v
Next sub_
Set ListFiles = out
End Function
Sub ImportAllCsv()
Dim files As Collection
Set files = ListFiles(ThisWorkbook.Path & "\data", "csv")
Dim i As Long
For i = 1 To files.Count
Debug.Print files(i)
Next i
End SubA recursive walk over a large directory tree is slow and can fail on a long path or a permission error mid-traversal. For a big tree, shell out to a short script or use Dir with a bounded recursion depth.
Importing CSV reliably
Sub ImportCsv(ByVal path As String, ByVal dest As Worksheet)
Dim f As Object, ts As Object
Set f = CreateObject("Scripting.FileSystemObject")
If Not f.FileExists(path) Then
Err.Raise vbObjectError + 1001, "ImportCsv", "file not found: " & path
End If
Dim lines As Collection
Set lines = New Collection
Set ts = f.OpenTextFile(path, 1, False, -2)
Do Until ts.AtEndOfStream
lines.Add ts.ReadLine
Loop
ts.Close
If lines.Count = 0 Then Exit Sub
' split into a 2D array, then write once: never partly fill the sheet
Dim header() As String
header = Split(lines(1), ",")
Dim cols As Long
cols = UBound(header) + 1
Dim data() As Variant
ReDim data(1 To lines.Count - 1, 1 To cols)
Dim i As Long, j As Long, fields() As String
For i = 2 To lines.Count
fields = Split(lines(i), ",")
For j = 1 To cols
If j - 1 <= UBound(fields) Then
data(i - 1, j) = fields(j - 1)
Else
data(i - 1, j) = ""
End If
Next j
Next i
dest.Range("A1").Resize(UBound(data, 1), cols).Value2 = data
End Sub
' WARNING: Split on a comma is wrong for a real CSV file.
' A quoted field may contain a comma or a newline:
' 1,"Smith, John",42
' A correct parser tracks the quote state character by character.
' Where you have a choice, use Data > From Text/CSV, or an ADODB recordset,
' which handle quoting, codepages and type inference for you.⚠️
Never write a partial result to a worksheet and then fail. Build the whole array in memory, then assign it in one statement. If the import throws halfway through, the sheet is left with the previous data intact rather than a confusing half-imported table.
FAQ
How do I write a UTF-8 file?
FileSystemObject cannot. Use CreateObject("ADODB.Stream"), set .Type = 2 (text) and .Charset = "utf-8", or call PowerShell with Set-Content -Encoding utf8 through WScript.Shell.Why is the file still locked after my macro finishes?
A
TextStream was not closed, usually because an error jumped past the Close call. Close it in a cleanup block that runs from the error handler as well as from the success path.Related
Error handling and the classic pitfalls Calling web APIs and parsing JSON in VBA
Last refreshed 2026-09-18.