BinToBat.bat - Easily install files of any type or size!
Posted: 09 Aug 2013 22:50
I take my old BinToHex.bat and HexToBin.bat conversion programs, that allows to encode and decode any binary file into Ascii digits, and modified they in order to keep them updated. The new encoding/decoding method can manage several data files in the same Batch program, as foxidrive suggested some time ago, and have also some features that are useful when installing a large number of files. I also optimized the JScript code so even large files, of about 10-15 MB size, can be encoded and decoded in a reasonably period of time.
EDIT: I fixed the small bug that foxidrive reported in his reply...
EDIT: I added an IF to avoid the inclusion of BinToBat.bat itself.
EDIT: Oct/16/2013: I modified the method to encode text files preserving they as text, so the resulting encoded file is lesser.
EDIT: Oct/20/2013: I added the ability to split a large output file in several parts of a maximum given size.
BinToBat.bat:
I personally don't like this approach because the resulting installer files are too large, but I must admit that this method is simpler to follow. However, I think that the new complaints will come now from the opposite way: "Why I must download a huge program if I only want a small file included in it?"
Antonio
EDIT: I fixed the small bug that foxidrive reported in his reply...
EDIT: I added an IF to avoid the inclusion of BinToBat.bat itself.
EDIT: Oct/16/2013: I modified the method to encode text files preserving they as text, so the resulting encoded file is lesser.
EDIT: Oct/20/2013: I added the ability to split a large output file in several parts of a maximum given size.
BinToBat.bat:
Code: Select all
@if (@CodeSection == @Batch) @then
@echo off
REM BinToBat.bat: Create an installer Batch program for binary and text data files
REM Antonio Perez Ayala
if "%~1" neq "" if "%~1" neq "/?" goto begin
echo Create an installer Batch program for data files of any type
echo/
echo BINTOBAT [/T:.ext1.ext2...] [/L:lineSize] [/F[:fileSize]] filename ...
echo/
echo /T:.ext1.ext2 Specify the extensions of text type files that will not be
echo encoded as hexadecimal digits, but preserved as text.
echo /L:lineSize Specify the size of output lines (default: 78).
echo /F[:fileSize] /F switch specify to generate a Full installer file.
echo The optional fileSize specify the maximum output file size.
echo/
echo BinToBat encode the given data files as hexadecimal digits (or preserve they
echo as compressed text) and insert they into InstallFiles.bat program; when this
echo program run, it generates the original data files.
echo/
echo You may rename the InstallFiles.bat program as you wish, but preserving the
echo "Install" prefix is suggested.
echo/
echo You may use wild-cards in the filename list.
echo/
echo If the /F switch is not given, a Partial installer is created:
echo - You may insert a short description for each file.
echo - You may insert divisions in the file listing via a dash in the parameters.
echo - The installer allows to select which files will be downloaded and ask
echo before overwrite existent files.
echo/
echo If the /F switch is given, a Full installer is created:
echo - The installer always download all files.
echo - You may specify commands that will be executed after the files were copied.
echo - You may specify the maximum size of the output file via /F:fileFize, so in
echo this case the output file will be divided in parts with a numeric postfix.
echo/
echo If you use /F switch you can NOT rename the InstallFiles??.bat files; the
echo first one is the installer and the rest just contain data.
echo/
goto :EOF
:begin
setlocal EnableDelayedExpansion
set atSign=@
rem Process parameters: BinToBat /T:.ext1.ext2 /L:lineSize /F:fileSize file list ...
set "myself=%~F0"
set "params=%*"
set "param=%~1"
set "textTypes="
if /I "%param:~0,3%" equ "/T:" (
set "textTypes=%param:~3%."
set "params=!params:* =!"
shift
)
set "param=%~1"
set lineSize=78
if /I "%param:~0,3%" equ "/L:" (
set "lineSize=%param:~3%"
set "params=!params:* =!"
shift
)
set "param=%~1"
if /I "%param:~0,2%" equ "/F" goto FullInstaller
:PartialInstaller
rem Create the heading part of output file
(
echo %atSign%if (@CodeSection == @Batch^) @then
call :getResource BatchSection-PartialInstaller
echo %atSign%end
call :getResource JScriptSection
echo %atSign%if (@CodeSection == @Resources^) @then
) > InstallFiles.bat
rem Convert each given file to hex digits and embed them in its resource
echo --^> Enter a short description for each division or file
echo/
set /A id=0, dId=0
for %%a in (%params%) do if /I "%%~NXa" neq "InstallFiles.bat" if /I "%%~NXa" neq "%~NX0" (
if "%%a" neq "-" (
if not exist "%%~a" (
echo File not found: %%a
) else (
set /A id+=1
set "desc= "
set /P "desc=!id!- %%~NXa: "
set type=notx
if defined textTypes if "!textTypes:%%~Xa.=!" neq "%textTypes%" set type=text
( rem Start of redirected block
echo/
echo ^<resource id="!id!|%%~Za|%%~NXa|!type!|!desc!"^>
Cscript //nologo //E:JScript "%myself%" !id! "%%~a" !type! !lineSize!
echo ^</resource^>
) >> InstallFiles.bat
echo %%~NXa file embedded
)
) else (
set /A dId-=1
set "desc= "
set /P "desc=- "
( rem Start of redirected block
echo/
echo ^<resource id="!dId!|0|- |div|!desc!"^>
echo ^</resource^>
) >> InstallFiles.bat
)
echo/
)
rem Create the trailing part of output file
(
echo/
echo :getLastID
echo set lastID=!id!
echo exit /B
echo/
echo %atSign%end
) >> InstallFiles.bat
goto endFile
:FullInstaller
rem Get the maximum file size from this parameter: /F:fileSize
for /F "tokens=2 delims=:" %%a in ("%param%") do set "maxSize=%%a"
if defined maxSize (
set /A maxSize-=10
) else (
set /A maxSize=0x7FFFFFFF
)
set "params=!params:* =!"
rem Create the heading part of output file
del InstallFiles??.bat 2> NUL
(
echo %atSign%if (@CodeSection == @Batch^) @then
call :getResource BatchSection-FullInstaller
) > InstallFiles00.bat
echo Enter commands that will be executed after the files were copied,
echo or an empty line to continue:
echo/
:nextCommand
set "command="
set /P "command=> "
if not defined command goto continue
echo !command!>> InstallFiles00.bat
goto nextCommand
:continue
( rem Start of redirected block
echo goto :EOF
echo/
echo End of Batch section
echo/
echo/
echo %atSign%end
call :getResource JScriptSection
echo %atSign%if (@CodeSection == @Resources^) @then
) >> InstallFiles00.bat
rem Sort files by size in descending order
for %%a in (%params%) do set "N=%%~Na" & if /I "!N:~0,12!%%~Xa" neq "InstallFiles.bat" if /I "%%~Fa" neq "%myself%" (
if not exist "%%~a" (
echo File not found: %%a
) else (
set /A fileSize=0x7FFFFFFF-%%~Za
set fileSize=000000000!fileSize!
set "fileName[!fileSize:~-10!-%%a]=1"
)
)
rem Convert each given file to hex digits or encoded text and embed they in its resource
echo/
set id=0
set lastFile=100
for /F "tokens=2 delims=-]" %%a in ('set fileName[') do (
set /A id+=1
set type=notx
if defined textTypes if "!textTypes:%%~Xa.=!" neq "%textTypes%" set type=text
( rem Start of redirected block
echo/
echo ^<resource id="!id!|%%~Za|%%~NXa|!type!| "^>
Cscript //nologo //E:JScript "%myself%" !id! "%%~a" !type! !lineSize!
echo ^</resource^>
) > InstallFiles.bat
rem Insert this data in the first output file with enough free space
for %%f in (InstallFiles.bat) do set increment=%%~Zf
for /L %%i in (100,1,!lastFile!) do if exist InstallFiles.bat (
set i=%%i
for %%f in ("InstallFiles!i:~1!.bat") do set /A newSize=%%~Zf+increment
if !newSize! leq %maxSize% (
type InstallFiles.bat >> InstallFiles!i:~1!.bat
del InstallFiles.bat
)
)
if exist InstallFiles.bat (
rem Create a new output file
set /A lastFile+=1
(
echo :InstallFiles!lastFile:~1!.bat
echo/
echo @goto :EOF
) > InstallFiles!lastFile:~1!.bat
if !increment! leq %maxSize% (
rem Insert this data in the new empty file
type InstallFiles.bat >> InstallFiles!lastFile:~1!.bat
del InstallFiles.bat
) else (
rem Split this data in several new files
echo Creating part !lastFile:~1!
set adjust=0
if !id! gtr 9 set adjust=1
set /A "line=0, maxLines=(maxSize-1)/(lineSize+4+adjust)+1"
for /F "delims=" %%b in (InstallFiles.bat) do (
set /A line+=1
if !line! equ !maxLines! (
echo ^</resource^>>> InstallFiles!lastFile:~1!.bat
set /A lastFile+=1, line=0
(
echo :InstallFiles!lastFile:~1!.bat
echo/
echo @goto :EOF
echo ^<resource id="!id!|%%~Za|%%~NXa|!type!| "^>
) > InstallFiles!lastFile:~1!.bat
echo Creating part !lastFile:~1!
)
(
setlocal DisableDelayedExpansion
echo %%b
endlocal
) >> InstallFiles!lastFile:~1!.bat
)
del InstallFiles.bat
)
)
echo %%~NXa file embedded
)
rem Create the trailing part of output file
(
echo/
echo %atSign%end
) >> InstallFiles00.bat
:endFile
echo/
echo InstallFiles.bat file(s) created
goto :EOF
:getResource resourceId
rem Resource data start format: ^<resource id="resourceId"^>
set start=
set lines=
for /F "tokens=1,3 delims=:=>" %%a in ('findstr /N "^</*resource" "%myself%"') do (
if not defined start (
if "%1" equ "%%~b" set start=%%a
) else (
if not defined lines set /A lines=%%a-start-1
)
)
set line=0
for /F "skip=%start% tokens=1* delims=]" %%a in ('find /N /V "" ^< "%myself%"') do (
setlocal DisableDelayedExpansion
echo(%%b
endlocal
set /A line+=1
if !line! equ %lines% exit /B
)
exit /B
End of Batch section
@end
// JScript section
// Convert original input file to encoded Stdout
// Antonio Perez Ayala
// Arguments: idNumber fileName type lineSize
var id = WScript.Arguments(0), lineSize = parseInt(WScript.Arguments(3));
if ( WScript.Arguments(2) == "text" ) {
// Convert input text file into an encoded text form in Stdout
// Each line is encoded as: <number of spaces from 3 to 29><line length>:<line contents>
var fso = new ActiveXObject("Scripting.FileSystemObject");
var input = fso.OpenTextFile(WScript.Arguments(1), 1); // ForReading = 1
var output = WScript.Stdout, outputLine = "", atSign, key, pos;
while ( ! input.AtEndOfStream ) {
var inputLine = input.ReadLine(), spaces = 0;
// Encode <number of spaces>
while ( inputLine.charAt(spaces) == " " ) spaces++;
if ( 3 <= spaces && spaces <= 29 ) {
outputLine += spaces;
inputLine = inputLine.substr(spaces);
} else {
outputLine += "0";
}
// Break JScript conditional compilation statements
atSign = 1;
while ( atSign ) {
atSign = inputLine.length;
for ( key in {"if":0,"elif":0,"else":0,"end":0} ) {
if ( pos = inputLine.indexOf("@"+key)+1 ) atSign=Math.min(atSign,pos);
}
if ( atSign < inputLine.length ) {
outputLine += atSign+":"+inputLine.substr(0,atSign)+"@";
inputLine = inputLine.substr(atSign);
} else {
atSign = 0;
}
}
// Encode joined <line length>:
if ( inputLine.length ) outputLine += inputLine.length;
outputLine += ":";
if ( outputLine.length >= lineSize ) {
output.WriteLine(id+":"+outputLine);
outputLine = "";
}
// Encode line contents
if ( inputLine.length ) outputLine += inputLine;
// Output result in segments
while ( outputLine.length > lineSize ) {
output.WriteLine(id+":"+outputLine.substr(0,lineSize));
outputLine = outputLine.substr(lineSize);
}
}
if ( outputLine.length ) output.WriteLine(id+":"+outputLine);
} else {
// Convert binary bytes from input file to Hex digits in Stdout
var ado = WScript.CreateObject("ADODB.Stream");
ado.Type = 2; // adTypeText = 2
ado.CharSet = "iso-8859-1"; // code page with minimum adjustments for input
ado.Open();
ado.LoadFromFile(WScript.Arguments(1));
var adjustment = "\u20AC\u0081\u201A\u0192\u201E\u2026\u2020\u2021" +
"\u02C6\u2030\u0160\u2039\u0152\u008D\u017D\u008F" +
"\u0090\u2018\u2019\u201C\u201D\u2022\u2013\u2014" +
"\u02DC\u2122\u0161\u203A\u0153\u009D\u017E\u0178" ;
var thisByte = ado.ReadText(1), lastByte = thisByte.charCodeAt(0), lastCount = 1, lastLen = 0,
output = WScript.Stdout, outputLine = "";
if ( lastByte > 255 ) lastByte = 128 + adjustment.indexOf(thisByte);
while ( ! ado.EOS ) {
var inputLine = ado.ReadText(128*1024);
for ( var index = 0; index < inputLine.length; index++ ) {
thisByte = inputLine.charCodeAt(index);
if ( thisByte > 255 ) thisByte = 128 + adjustment.indexOf(inputLine.charAt(index));
if ( thisByte == lastByte ) {
lastCount++;
} else {
lastLen = outputLine.length;
lastByte = ("0"+lastByte.toString(16)).slice(-2);
if ( (lastByte == "00" && lastCount == 1) || (lastByte != "00" && lastCount < 4 ) ) {
for ( var i = 1; i <= lastCount; i++ ) {
outputLine += lastByte;
}
} else {
outputLine += '[' + lastCount;
if ( lastByte != "00" ) outputLine += 'x' + lastByte;
outputLine += ']';
}
lastByte = thisByte;
lastCount = 1;
if ( outputLine.length > lineSize ) {
output.WriteLine(id+':'+outputLine.substr(0,lastLen));
outputLine = outputLine.substr(lastLen);
}
}
}
WScript.Stderr.Write((ado.Position/ado.Size*100).toFixed() + " % converted\r");
}
lastByte = ("0"+lastByte.toString(16)).slice(-2);
if ( lastCount < 4 ) {
for ( i = 1; i <= lastCount; i++ ) {
outputLine += lastByte;
}
} else {
outputLine += '[' + lastCount;
if ( lastByte != "00" ) {
outputLine += 'x' + lastByte;
}
outputLine += ']';
}
output.WriteLine(id+':'+outputLine);
ado.Close();
}
// End of JScript section
@if (@CodeSection == @Resources) @then
<resource id="BatchSection-PartialInstaller">
@echo off
setlocal EnableDelayedExpansion
if "%~1" neq "" if "%~1" neq "/?" goto begin
if "%~1" equ "" (
echo This program can install the following data files:
echo/
for /F tokens^=2-6^ delims^=^"^| %%a in ('findstr "^<resource" "%~F0"') do (
if %%a gtr 0 (
echo %%a- %%c %%e
) else (
echo/
echo - %%e
)
)
echo/
echo For help, type: %0 /?
) else (
echo This program is an installer of several data files included in it.
echo/
echo To show a list of the included files, type just: %0
echo/
echo Enter the numbers of the desired files using the same format of "tokens=..."
echo FOR command's option (type: FOR /? for further details^). For example:
echo/
echo Install just file 6: %0 6
echo Install files 2, 4 and from 7 to 9: %0 2,4,7-9
echo Install files from 14 to last one: %0 14*
echo Install all files: %0 *
echo/
echo/
echo Created using BinToBat.bat file written by Antonio Perez Ayala (aka Aacini^)
)
echo/
goto :EOF
:begin
call :getLastID
set argv=%*
if "%argv%" equ "*" (
set argv=1-%lastID%
) else (
if "%argv:~-1%" equ "*" (
if "%argv:~-2%" equ ",*" set argv=%argv:~0,-1%
set argv=!argv:~0,-1!-%lastID%
)
)
set idList=/
for %%a in (%argv%) do (
for /F "tokens=1,2 delims=-" %%b in ("%%a") do (
if "%%c" equ "" (
set idList=!idList!%%b/
) else (
for /L %%i in (%%b,1,%%c) do set idList=!idList!%%i/
)
)
)
set action=N
for /F tokens^=2-5^ delims^=^"^| %%a in ('findstr "^<resource" "%~F0"') do (
if "!idList:/%%a/=/!" neq "%idList%" (
set "output=%~DP0%%c"
if exist "!output!" (
if /I !action! neq A (
set /P "action=Overwrite %%c? (No/Yes/All): "
set "action=!action:~0,1!
)
set act=!action!
if /I !act! equ A set act=Y
if /I !act! equ Y del "!output!"
if /I !action! neq A set action=N
)
if not exist "!output!" (
echo/
echo Extracting %%c
findstr "^%%a:" "%~F0" | Cscript //nologo //E:JScript "%~F0" "!output!" %%b %%d
echo %%c file created
)
echo/
)
)
goto :EOF
End of Batch section
</resource>
<resource id="BatchSection-FullInstaller">
@echo off
setlocal EnableDelayedExpansion
cd "%~P0"
set lastFile=:dummy:
for %%f in (InstallFiles*.bat) do (
for /F tokens^=2-5^ delims^=^"^| %%a in ('findstr "^<resource" "%%f"') do (
if "%%c" neq "!lastFile!" (
echo Extracting %%c
set "part="
) else (
echo Appending next part
set part=part
)
findstr "^%%a:" "%%f" | Cscript //nologo //E:JScript InstallFiles00.bat "%%c!part!" %%b %%d
if defined part (
type "%%c!part!" >> "%%c"
del "%%c!part!"
echo Part appended
) else (
echo %%c file created
)
set "lastFile=%%c"
)
)
</resource>
<resource id="JScriptSection">
// JScript section
// Convert encoded Stdin to original output file
// Antonio Perez Ayala
// Arguments: fileName size type
if ( WScript.Arguments(2) == "text" ) {
// Convert encoded text from Stdin to output text file
// Each line is encoded as: <number of spaces from 3 to 29><line length>:<line contents>
var fso = new ActiveXObject("Scripting.FileSystemObject");
var output = fso.CreateTextFile(WScript.Arguments(0), true);
// 12345678901234567890123456789
var spaces = " ", cutId, digits, inputBuf;
var input = WScript.StdIn, inputLine = "", newLine = "", len;
while ( ! input.AtEndOfStream || inputLine.length ) {
if ( ! inputLine.length ) {
inputLine = input.ReadLine();
for ( cutId = 0; inputLine.charAt(cutId++) != ":"; );
inputLine = inputLine.substr(cutId);
}
len = inputLine.charAt(0);
inputLine = inputLine.substr(1);
if ( len == "1" || len == "2" ) {
len += inputLine.charAt(0);
inputLine = inputLine.substr(1);
}
if ( len != "@" ) output.Write(newLine+spaces.substr(0,parseInt(len)));
newLine = "\r\n";
for ( digits = 0; inputLine.charAt(digits) != ":"; digits++ );
len = digits ? parseInt(inputLine.substr(0,digits)) : 0;
inputLine = inputLine.substr(digits+1);
while ( inputLine.length < len ) {
inputBuf = input.ReadLine();
for ( cutId = 0; inputBuf.charAt(cutId++) != ":"; );
inputLine += inputBuf.substr(cutId);
}
output.Write(inputLine.substr(0,len));
inputLine = inputLine.substr(len);
}
output.WriteLine();
output.Close();
} else {
// Convert Hex digits from Stdin to binary bytes in output file
var ado = WScript.CreateObject("ADODB.Stream");
ado.Type = 2; // adTypeText = 2
ado.CharSet = "iso-8859-1"; // right code page for output (no adjustments)
ado.Open();
var input = WScript.StdIn, cutId = 0, outputLine = "", byte, count;
var size = parseInt(WScript.Arguments(1));
while ( ! input.AtEndOfStream ) {
var inputLine = input.ReadLine();
if ( cutId == 0 ) while ( inputLine.charAt(cutId++) != ':' );
for ( var index = cutId; index < inputLine.length; ) {
if ( inputLine.charAt(index) == '[' ) {
for ( count = ++index; inputLine.charAt(index) != 'x' &&
inputLine.charAt(index) != ']' ; index++ ) ;
count = parseInt(inputLine.slice(count,index++));
if ( inputLine.charAt(index-1) == 'x' ) {
byte = String.fromCharCode(parseInt(inputLine.substr(index,2),16));
index += 3;
} else {
byte = String.fromCharCode(0);
}
for ( var i = 1; i <= count; i++ ) {
outputLine += byte;
}
} else {
outputLine += String.fromCharCode(parseInt(inputLine.substr(index,2),16));
index += 2;
}
}
if ( outputLine.charAt(32*1024) != "" ) {
ado.WriteText(outputLine);
outputLine = "";
WScript.Stderr.Write((ado.Position/size*100).toFixed() + " % converted\r");
}
}
if ( outputLine.length ) ado.WriteText(outputLine);
ado.SaveToFile(WScript.Arguments(0),2); // adSaveCreateOverWrite = 2
ado.Close();
}
// End of JScript section
</resource>
@end
I personally don't like this approach because the resulting installer files are too large, but I must admit that this method is simpler to follow. However, I think that the new complaints will come now from the opposite way: "Why I must download a huge program if I only want a small file included in it?"
Antonio