Binary Files and the File System Object Do Not Mix

Binary Files and the File System Object Do Not Mix

  • Comments 33

OK, back to scripting today.

But before I get back to scripting issues, one brief correction. An attentive reader noted that "The Well-Tempered Clavier" was in fact designed to sound good on a "well tempered" instrument, not an "equally tempered" instrument. The difference is that a "well" temperament is designed so that every key sounds good, but is allowed to have some badly-out-of-tune intervals that must be avoided. (Traditionally these are called "wolf intervals".)

There was considerable controversy when equal temperament was introduced in Europe. I suppose it was the "what is the One True Bracing Style?" ridiculous issue of the day.

Another commenter pointed out that you could translate my wav-writing program into VBScript by using the File System Object to write out the bytes. To simplify their code down to a program that writes out individual bytes:

' DO NOT DO THIS
Set FSO=CreateObject("Scripting.FileSystemObject")
Set File=FSO.CreateTextFile("c:\test.bin", True)
For i = 0 to 255
  File.Write Chr(i)
Next
File.Close

And sure enough, this writes out a binary file consisting of those bytes.

Please don't do that. See that line that says "CreateTextFile"? We wrote that method to create a text file, not a binary file. Though this code might appear to work, it actually does not. Text files are more than just binary files that can be interpreted as text. Text files have to conform to certain rules to ensure that they can be sensibly interpreted as text in the local code page. If that's not 100% clear to you, read Joel's article on the subject before we go on.

Let me give you an example that clearly fails. What does this program do?

Set FSO=CreateObject("Scripting.FileSystemObject")
Set File=FSO.CreateTextFile("c:\test.bin", True)
For i = 0 to 255
  File.Write Chr(&hE0)
Next
File.Close

If you said "it writes out a binary file consisting of 256 E0 bytes," bzzt! Sorry, try again. The correct answer is "it writes out a binary file consisting of 256 E0 bytes on any operating system where the user's default ANSI code page does not define E0 as a lead byte in a DBCS encoding, like, say, Japanese, in which case it writes out 256 zeros."

In the Japanese code page, just-plain-chr(E0) is not even a legal character, so Chr will turn it into a zero. 

If I were whipping up a little one-off program on my own to write out a binary file -- well, I'd personally do it in C, but I can see how some people might want to do it in script. But there's a big difference between writing a one-off program that you're going to delete in five minutes, and writing a general-purpose utility program that you expect people around the world will use. That's an entirely different standard of robustness and portability. Do not use the FSO to read/write binary files, you're just asking for a world of hurt as soon as someone in DBCS-land runs your code.

I have been asked many times over the years if I know of a scriptable object that can read-write true binary files in all locales. I do not. Anyone have any suggestions? I would have thought given the number of people that have asked me, that some third party would have come up with something decent by now.

  • How can vb create a byte array? as.

    bytes = ChrB(1) & ChrB(1)

    ..is still a string.
  • In Visual Basic you create a byte array by, well, creating a byte array.

    dim b() as Byte

    In VBScript there is no way to create a byte array.  VBScript only supports arrays of variants.

    In VBScript if you create a string that contains binary data, and then pass that to an ActiveX object which expects a byte array, the default implementation of IDispatch::Invoke provided by the operating system will turn the binary string into a byte array for you.  So maybe that sneaky trick will work for you.  But my advice would be that if you need to create a byte array, the best thing to do would be to use a language which has byte arrays -- VB, C#, C++, etc.
  • Eric,
    you mentioned that creating a string that contains binary data would satisfy a COM method that expects a byte array. That is exactly what I am seeking. Could you please give an example of code that would create such string from a conventional VBScript string, say, "Hello World"? I am not "native" to ASP/VB, so maybe it's common knowledge for those who are in that world; sorry about that.
    Thanks!
  • I found out a way to create an array of bytes in VBScript using ADODB.Stream object mentioned above, which resolved the problem I had. Thanks!
  • EricLippert said:
    How do you create the binary array?

    One way is to create a Text ADODB.Stream and copy it into a Binary ADODB.Stream.
    Consider the following snippet I recently wrote to update a database connection UDL file:

      Option Explicit

      Dim sServer, sDatabase, sUsername, sPassword
      sServer   = "servername"
      sDatabase = "database"
      sUsername = "username"
      sUsername = "password"

      Dim UDL
      UDL = ReadBinaryFile("Old.udl")
      UDL = SetValue(UDL, "Data Source", sServer)
      UDL = SetValue(UDL, "Initial Catalog", sDatabase)
      UDL = SetValue(UDL, "User ID", sUsername)
      UDL = SetValue(UDL, "Password", sPassword)
      UDL = SaveBinaryData("New.udl", UDL)

    ' =================================================================
    ' Function to read text from a binary (unicode) file
    ' =================================================================

      Function ReadBinaryFile(FileName)
         Const adTypeBinary = 1

         Dim BinaryStream
         Set BinaryStream = CreateObject("ADODB.Stream")
         BinaryStream.Type = adTypeBinary
         BinaryStream.Open
         BinaryStream.LoadFromFile FileName
         ReadBinaryFile = BinaryStream.Read
         BinaryStream.Close
         Set BinaryStream = Nothing
      End Function

    ' =================================================================
    ' Function to write a modified string back to a unicode file
    ' =================================================================

      Function SaveBinaryData(FileName, Text)
         Const adTypeBinary = 1
         Const adTypeText = 2
         Const adSaveCreateOverWrite = 2

         Dim BinaryStream
         Set BinaryStream = CreateObject("ADODB.Stream")
         BinaryStream.Type = adTypeBinary
         BinaryStream.Open
         With CreateObject("ADODB.Stream")
            .Type = adTypeText
            .Open: .WriteText Text
            .Position = 2
            .CopyTo BinaryStream, Len(Text) * 2
            .Close
         End With
         BinaryStream.SaveToFile FileName, adSaveCreateOverWrite
         BinaryStream.Close
         Set BinaryStream = Nothing
      End Function

    ' =================================================================
    ' Function replace semicolon delimited values in a unicode string
    ' =================================================================

      Function SetValue(Data, Key, Value)
         Dim Text, Prefix, Suffix, i
         If Len(Value) = 0 Then
            SetValue = Data
         Else
            'Drop leading character
            Text = Mid(Data, 2)
            i = InStr(Text, Key)
            Prefix = Left(Text, i + Len(Key))
            Suffix = Mid(Text, i + Len(Key))
            i = InStr(Suffix, ";")
            if i = 0 Then
               Suffix = ""
            Else
               Suffix = Mid(Suffix, i)
            End If
            'Restore leading character and concatinate new value
            SetValue = Left(Data, 1) + Prefix + Value + Suffix
         End If
      End Function
  • I've been playing with SideBar gadgets for some time now. Besides some quirks (ok, they're really bugs

  • Here's how to make a byte array, I extracted this from the example given by Hir

    Function VariantArrayToByteArray(arr)

    dim DM, EL, bin

    Set DM = CreateObject("Microsoft.XMLDOM")

    Set EL = DM.createElement("tmp")

    EL.DataType = "bin.hex"

    EL.Text = ArrayToHexString(arr)

    bin = EL.NodeTypedValue

    VariantArrayToByteArray = bin

    End Function

  • You will also need thi function:

    Function ArrayToHexString(arr)

     Dim I, B

     Redim B(UBound(arr))

     For I= 0 to UBound(arr)

       B(I) = right("0" & hex(arr(I)), 2)

     Next

     ArrayToHexString = Join(B,"")

    End Function

  • I have ie6 on my machine. I tried using adodb.stream from vbs for manipulating

    binary files. I understand that adodb.stream is not getting recognized and

    I am unable to save or read binary files through this. any ideas??

  • Try using an HTA or VBS file.

    IE6 block dynamic content (i.e. scripts) in HTM, HTML files

  • Surely it is better to call it MBCS-land... and anyway, aren't we all in an MBCS land these days? Everything except the most basic of basic text files (that do not include any currency symbols other than the dollar sign ;-))

  • I came across newObjects' AXPack1 when I found out that Windows CE doesn't have FSO. It seems to have great support for Binary files. It's free too.

    Check out this VBS sample:

    Dim fso, file, BD, BoM

    Set fso = CreateObject("newObjects.utilctls.SFMain")

    Set file = fso.OpenFile(filename)

    Set BD = CreateObject("newObjects.utilctls.SFBinaryData")

    BD.Value = file.ReadBin(2)   'read first 2 bytes

    BoM = BD.Data(0,1)   'convert first 2 bytes to byte array

    file.Close()

    bom now contains the byte order mark of filename in a VT_UI | VT_ARRAY byte array.

  • If ADODB.Stream is a solution.

    I dont know what is much ado aabout nothing means.

    /****************************************************************************/

     Option Explicit

     Dim sServer, sDatabase, sUsername, sPassword

     sServer   = "servername"

     sDatabase = "database"

     sUsername = "username"

     sUsername = "password"

     Dim UDL

     UDL = ReadBinaryFile("Old.udl")

     UDL = SetValue(UDL, "Data Source", sServer)

     UDL = SetValue(UDL, "Initial Catalog", sDatabase)

     UDL = SetValue(UDL, "User ID", sUsername)

     UDL = SetValue(UDL, "Password", sPassword)

     UDL = SaveBinaryData("New.udl", UDL)

    ' =================================================================

    ' Function to read text from a binary (unicode) file

    ' =================================================================

     Function ReadBinaryFile(FileName)

        Const adTypeBinary = 1

        Dim BinaryStream

        Set BinaryStream = CreateObject("ADODB.Stream")

        BinaryStream.Type = adTypeBinary

        BinaryStream.Open

        BinaryStream.LoadFromFile FileName

        ReadBinaryFile = BinaryStream.Read

        BinaryStream.Close

        Set BinaryStream = Nothing

     End Function

    ' =================================================================

    ' Function to write a modified string back to a unicode file

    ' =================================================================

     Function SaveBinaryData(FileName, Text)

        Const adTypeBinary = 1

        Const adTypeText = 2

        Const adSaveCreateOverWrite = 2

        Dim BinaryStream

        Set BinaryStream = CreateObject("ADODB.Stream")

        BinaryStream.Type = adTypeBinary

        BinaryStream.Open

        With CreateObject("ADODB.Stream")

           .Type = adTypeText

           .Open: .WriteText Text

           .Position = 2

           .CopyTo BinaryStream, Len(Text) * 2

           .Close

        End With

        BinaryStream.SaveToFile FileName, adSaveCreateOverWrite

        BinaryStream.Close

        Set BinaryStream = Nothing

     End Function

    ' =================================================================

    ' Function replace semicolon delimited values in a unicode string

    ' =================================================================

     Function SetValue(Data, Key, Value)

        Dim Text, Prefix, Suffix, i

        If Len(Value) = 0 Then

           SetValue = Data

        Else

           'Drop leading character

           Text = Mid(Data, 2)

           i = InStr(Text, Key)

           Prefix = Left(Text, i + Len(Key))

           Suffix = Mid(Text, i + Len(Key))

           i = InStr(Suffix, ";")

           if i = 0 Then

              Suffix = ""

           Else

              Suffix = Mid(Suffix, i)

           End If

           'Restore leading character and concatinate new value

           SetValue = Left(Data, 1) + Prefix + Value + Suffix

        End If

     End Function

    July 11, 2006 7:11 PM

    /****************************************************************************/

  • hem. just test that FileSystemObject can be use to READ and WRITE binary file. only that you had to CHEAT a little. Instead of using ReadAll() just use recursice Read() to the file Size. a little snippet in JScript:

    -- Code Start --

    var objFileSystem = new ActiveXObject('Scripting.FileSystemObject');

    var objFileIO = objFileSystem.GetFile('pack.exe');

    var streamIO = objFileIO.OpenAsTextStream();

    var strTransform = new Array();

    for (i=0;i<objFileIO.Size;i++) {

    var strContent = streamIO.Read(1);

    strTransform[i] = strContent.charCodeAt(0);

    }

    streamIO.Close();

    objFileIO = objFileSystem.CreateTextFile('dumb.exe', true);

    for (i=0;i<strTransform.length;i++) { objFileIO.Write(strTransform[i]); }

    objFileIO.Close();

    -- Code End --

    Basicaly copy pack.exe into Array then write Array into dumb.exe

    Surprisingly both are identical and executable. Isn't strange?

    All i need now is smart way to play with the Array ....

  • ups. Sorry ... a typo in the code. just replace:

    strTransform[i] = strContent.charCodeAt(0);

    into

    strTransform[i] = strContent;

    I use charCodeAt to play around with ASCII code .....

Page 2 of 3 (33 items) 123