A modern replacement for Scripting Runtime's FileSystemObject, written in twinBASIC.
It does everything FSO does, plus many things FSO cannot: full text-encoding support, files larger than 2 GB, line-ending detection and normalization, and long paths beyond the legacy 260-character limit.
' Read a UTF-8 file with a BOM, a UTF-16LE file, and a Shift-JIS file.
' You don't have to know which is which.
Dim text As String
text = TextFileToString("data.txt") ' encoding auto-detectedScripting.FileSystemObject shipped in 1996 and shows it:
| Feature | FSO | tbFileSysTools |
|---|---|---|
| Text encodings | ANSI and UTF-16 only | Any Windows code page, with BOM handling and auto-detection |
| Files > 2 GB | Reads them as empty, silently | Streams them, in both directions |
| Appending to a > 2 GB file | Not possible | Works |
| Line endings | No support | Detect, preserve, normalize |
| Paths > 260 chars | Fails, and FileExists returns False on a file that exists |
Supported transparently |
The claims above were measured through comprehensive testing.
The library comes in three forms — pick whichever fits how you work:
| Form | Use it when | Updates |
|---|---|---|
| twinBASIC package | You want a referenced .twinpack, no copied code |
Automatic via the package server |
| Single-file drop-in | You want one .twin in your project, no reference |
Manual |
| ActiveX DLL | You're calling from VBA, twinBASIC, or another COM host | Re-install the DLL (Inno script included) |
The package can be referenced through twinBASIC's Package Server. In the IDE, click References -> Available Packages. Scroll to find the entry and click the checkbox on the left of the entry. Then click "Apply Changes" button in lower-right.
The library has one implementation and two pathways to it.
Preferred for twinBASIC code. Call it directly; there's no object to create. Potentially smaller compile footprint than the object version.
If FileExists(path) Then
Debug.Print GetFile(path).Size
End IfA drop-in replacement for the Scripting Runtime object. Use it for hosts that need an object, or when porting existing FSO code that you'd rather not rewrite.
Dim fso As New FileSystemObject
Debug.Print fso.GetFile(path).SizeThe class is a one-line delegation to the module for every member — same behavior, same defaults. It uses FSO's argument names (fileSpec, folderSpec) so named arguments in ported code keep working.
Note: the class is deliberately named
FileSystemObject, the same as Scripting's. In a project referencing both, qualify it -tbFileSysTools.FileSystemObject, drop the Scripting Runtime reference, or move tbFileSysTools' position in the References dialog ABOVE Scripting Runtime so that it takes unqualified precedence.
The headline feature. FSO can read system ANSI and UTF-16. This reads anything Windows has a code page for.
' Auto-detect: BOM first, then UTF-16/32 and UTF-8 heuristics, then system ANSI.
Dim s As String
s = TextFileToString("mystery.txt")
' Or be explicit. A BOM in the file still wins.
s = TextFileToString("legacy.txt", encGB18030)
' Write with a BOM by choosing a BOM-bearing encoding.
StringToTextFile s, "out.txt", encUtf8Bom
' What encoding IS this file?
Debug.Print GetFileEncoding("mystery.txt") ' e.g. encUtf16BomSupported: UTF-8, UTF-16 LE/BE, UTF-32 LE/BE, UTF-7, GB2312, GB18030, Big5, Latin-1, Latin-9, US-ASCII, system ANSI — each with and without a BOM (if applicable) — plus any other code page installed on the machine.
Auto-detection on Read. Defaults to auto-detect using a reliable heuristical algorithm but user can specify a code-page if known.
Windows (CRLF), Unix (LF) and classic-Mac (CR) endings are all supported and auto-detected on read — and a file that mixes styles is reported as mixed rather than guessed at. You don't have to know a file's convention to read, normalize, or append to it.
Debug.Print GetFileLineEnding("script.sh") ' nlUnix
' Rewrite in place: UTF-8, CRLF, attributes preserved. Skips the write if the
' file is already in that form.
NormalizeTextFile "script.sh", encUtf8, nlWindowsAppending to an existing file adopts that file's newline style, so you can't accidentally turn a clean file into a mixed one.
FSO cannot read a file larger than 2 GB. It doesn't error — it returns an empty string. This library streams them.
Dim ts As TextStream
Set ts = OpenTextFile("huge.log", ForReading)
Do Until ts.AtEndOfStream
ProcessLine ts.ReadLine() ' no size limit
Loop
ts.Close
' Appending to a 4 GB file also works.
Set ts = OpenTextFile("huge.log", ForAppending)
ts.WriteLine "another line"
ts.CloseReadLine, Read, Write and append are unbounded. ReadAll is capped at 2 GB by the size of a VB String and raises a clear error rather than misbehaving — use ReadLine for anything larger.
Multi-byte encodings are handled correctly across chunk boundaries, including surrogate pairs split by a 64 KB read. This is verified against 54 million lines of mixed 1-, 2-, 3- and 4-byte characters.
Windows' legacy MAX_PATH limit is 260 characters. Scripting.FileSystemObject is bound by it — on a longer path, FileExists returns False for a file that exists, and GetFile and OpenTextFile raise "path not found". This library supports long paths transparently: reading, writing, creating, copying, moving, deleting, enumerating, normalizing and merging all work well past 260 characters. You pass a normal path; the library canonicalizes it and applies the \\?\ prefix to the underlying Win32 calls when needed. No prefix, no flag, no manifest — it works regardless of the LongPathsEnabled registry setting, and identically with it enabled.
A few members remain 260-bound because the specific Win32 APIs behind them don't accept the prefix: GetFileType, GetFileVersion, GetRelativePath, File.ShortPath, SetCurrentDir, and wildcard patterns (matched items are unaffected). Each degrades or raises clearly rather than returning a wrong answer — see each member's description for its exact behavior.
' A 400-plus-character path is just a path.
Dim deep As String
deep = "C:\...\a\very\deeply\nested\...\structure\notes.txt" ' > 260 chars
StringToTextFile "hello", deep
Debug.Print FileExists(deep) ' TrueFile, Folder, Drive and their collections work as they do in FSO.
Dim f As Folder
Set f = GetFolder("C:\Projects")
Debug.Print f.Size ' total bytes, subtree
For Each fl In f.Files
Debug.Print fl.Name, fl.Size, fl.DateLastModified
NextFile and Folder objects are live: every property read re-stats the path, so values are never stale — and a deleted file raises rather than reporting stale values. The trade-off is that each property read costs a round trip, so hoist values out of tight loops.
Folder.Files and Folder.SubFolders return a snapshot of the membership. The objects inside are live; the list is not.
Parity is the goal, but not at any price. Each of these was checked against the Scripting.FileSystemObject, and broken deliberately:
Folder.Size does not follow directory reparse points. FSO does. A junction pointing into its own subtree makes FSO double-count (measured: 2500 vs 1500), and a junction pointing at an ancestor makes it recurse until it dies. This library reports what physically lives in the tree.
An unreadable folder raises. FSO silently reports it as empty. A size or file list that quietly omits a subtree that can't be read is worse than an error.
FileAttribute.Volume and .Alias are not provided. Volume has no Win32 equivalent (use Drive.VolumeName). Alias is FILE\_ATTRIBUTE\_REPARSE\_POINT under a misleading name — and collides with VBA's vbAlias (64 vs 1024). Use ReparsePoint.
The following members are either added, or their function significantly improved.
| Member | Enhancement Description |
|---|---|
CreateTextFile |
Create any format - not just ANSI/UTF-16 |
OpenTextFile |
Format auto-detection or user-specified code page |
CreateFolder |
Optionally create path intermediates |
GetFileEncoding |
Detect a file's encoding |
GetFileLineEnding |
Detect CRLF / LF / CR / mixed |
MergeTextFiles |
Merges two text files, normalizing the encoding to the first |
NormalizeTextFile |
Rewrite encoding + newlines in place, idempotently |
GetFilePaths |
Enumerate with a wildcard, recursion, hidden/system filters |
GetRelativePath |
Path from A to B |
CleanFileName |
Strip reserved characters and device names |
DeleteFile / DeleteFolder |
optional ignoreMissing argument |
Rename |
In-place rename |
GetFileType |
Shell type description ("Text Document") |
GetCurrentDir / SetCurrentDir |
Gets/Sets the current directory or folder. |
GetSpecialFolder |
25 known folders, including FSO's 3 |
ReadStream / WriteStream |
Raw bytes, Unicode-safe |
TextFileToString / StringToTextFile |
Whole-file text I/O |
TextFileToArray / ArrayToTextFile |
Whole-file line I/O |
File.Encoding |
Detect a file's encoding |
File.HasAttribute |
Determines if an attribute is set |
File.LineEnding |
Detect CRLF / LF / CR / mixed |
File.Normalize |
Rewrite encoding + newlines in place, idempotently |
File.OpenAsTextStream |
Format auto-detection or user-specified codepage |
File.SetAttribute |
Sets a single attribute |
File.ToStream |
Reads file to byte array |
File.ToString |
Reads file to string |
File.Version |
Gets the file version string |
Folder.HasAttribute |
Determines if an attribute is set |
Folder.SetAttribute |
Sets a single attribute |
TextStream.IsStreaming |
Whether byte-streaming or buffered access is supported |
TextStream.Encoding |
Returns a file's encoding |
| File | Description |
|---|---|
FileSystemTools |
The API where all the logic lives |
FileSystemObject |
COM-creatable thin wrapper class over the above |
TextStream |
Streaming text reader/writer |
TextCodec |
Encoding detection, encode/decode, BOM handling |
FSTShared |
Shared file/folder/drive procs |
WinAPI |
WinDevLib's Win32 declarations - not needed if referenced to WinDevLib |
File, Folder, Drive |
Objects |
Files, Folders, Drives |
Collections |
- fafalone — for WinDevLib, source of standardized Win32 API declares.
- Wayne Phillips — for twinBASIC.
MIT © 2026 GCUser99
