Search code examples
javascriptadodbjscriptwsh

How can I create a binary file and save it to local file system in WSH (JScript)?


I've been a whole day looking for a way to read a binary file from the local file system, do some modifications to it and save it back to disk. The script must be run on batch, since other scripts must be run afterwards.

Reading is not an issue: you can always read it as text, and then convert the characters to bytes. The problem is writting it to disk. ActiveXObject("Scripting.FileSystemObject") facility can't be used, since some bytes don't map to characters and then Write methods throw an exception. I've read a post, somewhere else suggesting to use ADODB.Stream and this is as far as I can go:

var foo = ...
var stream = new ActiveXObject('ADODB.Stream');
stream.Type = 1; //Means "binary".
stream.Open();
stream.Write(foo);
stream.SaveToFile('C:\\foo.bin', 2); //2 means save/create/overwrite

Regardles of which type of variable I put in foo, Windows Script Host claims:

Error:    Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.
Code:     800A0BB9
Source:   ADODB.Stream

It seems that ADODB Stream.Write method expects A Variant that contains an array of bytes to be written. Since such things doesn't exist in Javascript, I tried with an array filled with numbers, a string, a single number, a single char, an hex expression... I've found VBArray but you can't actually write anything in those.

Does anybody know which type must be used? Or any other mean to save binary data?


Solution

  • Adapted from ADODB.Stream writing binary file, JScript only. Commented for explanation in code.

    var adTypeBinary = 1 
    var adTypeText   = 2 
    var adSaveCreateOverWrite = 2
    var stdout = WScript.StdOut;
    
    // Read a binary file, particularly for debugging purposes
    var inStream = new ActiveXObject("ADODB.Stream");
        inStream.Type = adTypeBinary;
        inStream.Open();
        inStream.LoadFromFile("d:\\bat\\cliParser.exe");
        inStream.Position = 0;
    var binData = inStream.Read();
        inStream.Close();
    
    stdout.WriteLine( "binData " + typeof(binData)); // returns: "undefined"
    
    // Convert binary value of "undefined" data type to "string" data type
    var objRS = new ActiveXObject("ADODB.Recordset");
    var DefinedSize = 1024; /* A Long value that represents the defined size, 
          in characters or bytes, of the new field. Fields that have a DefinedSize 
          greater than 255 bytes are treated as variable length columns. */
    var adFldLong = 0x80;   /* Indicates that the field is a long binary field.  
          Also indicates that you can use the AppendChunk and GetChunk methods. */
    var adVarChar = 201;   /* Indicates a long string value. */
        objRS.Fields.Append("test", adVarChar, DefinedSize, adFldLong);
        objRS.Open();
        objRS.AddNew();
        objRS.Fields("test").AppendChunk(binData);
    var binString = objRS("test").value;
        objRS.close();
    
    stdout.WriteLine( "binString " + typeof(binString));  // returns: "string"
        // String is now manipulable (unlike "undefined" data type)
    
    // Write string to a file (converting string in one-byte encoding schema)
    
    var outStreamW = new ActiveXObject("ADODB.Stream");
        outStreamW.Type = adTypeText;
          // Charset: the default value seems to be `UTF-16` (BOM `0xFFFE` for text files)
        outStreamW.Open();
        outStreamW.WriteText(binString);
        outStreamW.Position = 0;
    
    var outStreamA = new ActiveXObject("ADODB.Stream");
        outStreamA.Type = adTypeText;
        outStreamA.Charset = "windows-1252"; // important, see `cdoCharset Module Constants`
        outStreamA.Open();
    
        outStreamW.CopyTo(outStreamA);      // convert encoding
    
        outStreamA.SaveToFile("D:\\test\\fooRus.exe", adSaveCreateOverWrite);
    
        outStreamW.Close();
        outStreamA.Close();
    
    // Done. Make certain of input and output file oneness!
    

    Output:

    ==> cscript D:\VB_scripts\JScripts\33330187my.js
    binData unknown
    binString string
    
    ==> echo n|COMP "d:\bat\cliParser.exe" "D:\test\fooRus.exe" 2>NUL
    Comparing D:\bat\cliParser.exe and D:\test\fooRus.exe...
    Files compare OK
    
    ==>