Page 1 of 1

bin2enc.cmd - Native .CAB & Self-Extracting Scripts

Posted: 24 Sep 2018 20:27
by CirothUngol
I've decided to place the updated script in the first post. Thanks for the suggestions, Carlos! ^_^
Added commandline options and a brief help. Please suggest better MakeCab settings if anyone knows them. I've spent some time attempting to navigate it's vast myriad of options, but I'm far from knowledgeable. Any help is much appreciated.

I've updated the script again adding Extract/Extrac32 support for WinNT systems with older (pre 6.x) versions of Expand, which apparently doesn't extract paths. Extract32 seems to be on nearly all systems, but produces a pop-up. Extract can be found in the MS Cabinet SDK (cabsdk) and doesn't produce a pop-up, just place it in the path. CertUtil for WinXP can be found in the Windows Server 2003 Administration Tools (adminpak). Tested on a WinXP SP2 x86 VMWare appliance.

Code: Select all

::-------------------------------------------------------------------------------
:bin2enc [/Cn /Kn /Fn] source\folder[\file] [destination\folder]            v0.3
:: 
::  Generates .CAB or self-extracting batch script from a source file or folder
::  using MakeCab + Expand/Extract (compression) + CertUtil (Base64 encoding).
:: 
::  /Cn compression 0=default=make both and keep smallest, 1=MSZIP, 2=LZX
::  /Kn keepSrcDir  0=default=ignore folder, 1=retain source folder in archive
::  /Fn fileType    0=default=make archive.cab, 1=make self-extracting.cmd
::-------------------------------------------------------------------------------
::
:: v0.3
:: Corrected extraction to use Extract/Extrac32 if installed version of
:: Expand is lower than 6.x, as previous versions ignore paths in CABs.
:: Cleaned up 'best compression' code just a bit by removing redundancies.
::
:: v0.2
:: Added commandline options, auto-choose MSZIP/LZX best compression,
:: and improved the MakeCab.ddf settings.
::
@ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET "compression=0"   ' 0=make both and keep smallest, 1=MSZIP, 2=LZX
SET "keepSrcDir=0"    ' 0=ignore folder, 1=retain source folder in archive
SET "fileType=0"      ' 0=make archive.cab, 1=make self-extracting.cmd
SET "targetDir=%~dp0" ' default destination folder for creating files
SET "SELF=%~f0"

:bin2enc_parse
:: /Cn /Kn /Fn else shift and ignore
SET "par=%~1"
IF /I "!par:~0,1!"=="/" ( SHIFT
	IF /I "!par:~1,1!"=="C" SET /A "compression=!par:~2!" 2>NUL
	IF /I "!par:~1,1!"=="K" SET /A "keepSrcDir=!par:~2!" 2>NUL
	IF /I "!par:~1,1!"=="F" SET /A "fileType=!par:~2!" 2>NUL
	GOTO :bin2enc_parse
)

:: exit if no input, input doesn't exist, or file count=0
SET count=0
IF NOT EXIST "%~1" GOTO :bin2enc_help
FOR /F %%A IN ('DIR /B "%~f1" 2^>NUL') DO SET /A "count+=1"
IF %count% EQU 0 GOTO :bin2enc_help

:: get full path to source/target
FOR %%A IN ("%~1") DO SET "source=%%~fA" & SET "sPath=%%~pnxA" & SET "cabName=%%~nxA"
FOR %%A IN ("%~2") DO SET "target=%%~fA"
IF NOT DEFINED target SET "target=!targetDir!"
MD "!target!" 2>NUL
IF %keepSrcDir% EQU 0 (SET "destDir=") ELSE SET "destDir=!cabName!"

:: Add the header to the cabinet config file
IF %compression% LEQ 1 (SET "ct=MSZIP") ELSE SET "ct=LZX"
SET "targetFile=!target!\!cabName!"
>"!targetFile!.ddf" (
	ECHO(; Generated on %DATE% at %TIME: =0%
	ECHO(.New Cabinet
	ECHO(.Set CabinetNameTemplate="!cabName!.cab"
	ECHO(.Set DiskDirectoryTemplate="!target!"
	ECHO(.Set GenerateInf=OFF
	ECHO(.Set Cabinet=ON
	ECHO(.Set Compress=ON
	ECHO(.Set UniqueFiles=ON
	ECHO(.Set MaxDiskSize=1215751680
	ECHO(.Set RptFileName=NUL
	ECHO(.Set InfFileName=NUL
	ECHO(.Set MaxErrors=1
	ECHO(.Set CompressionMemory=21
)

:: if source is folder add files iteratively
IF EXIST "!source!\" (
	FOR /D /R "%source%" %%A IN (*) DO (
		SET "tDir=%%~pnxA"
		SET "dDir=!destDir!!tDir:%sPath%=!"
		ECHO(Adding !cabName!!tDir:%sPath%=!
		ECHO(.Set DestinationDir=!dDir!>>"!targetFile!.ddf"
		FOR %%B IN ("%%A\*") DO ECHO("%%~fB"  /inf=no>>"!targetFile!.ddf"
	)
	ECHO(Adding !cabName!
	ECHO(.Set DestinationDir=!destDir!>>"!targetFile!.ddf"
	FOR %%A IN ("!source!\*") DO ECHO("%%~fA"  /inf=no>>"!targetFile!.ddf"
	ECHO(
) ELSE ECHO("!source!"  /inf=no>>"!targetFile!.ddf"
ECHO(CompressionType=!ct!
MakeCab /D CompressionType=!ct! /F "!targetFile!.ddf"

:: second compression
IF %compression% LEQ 0 ( ECHO(
	ECHO(CompressionType=LZX
	REN "!targetFile!.cab" "!cabName!.zip.cab"
	MakeCab /D CompressionType=LZX /F "!targetFile!.ddf"
	FOR %%A IN ("!targetFile!.cab") DO FOR %%B IN ("!targetFile!.zip.cab") DO (
		IF %%~zA GEQ %%~zB ( DEL /F /A "!targetFile!.cab"
			REN "!targetFile!.zip.cab" "!cabName!.cab" 
		) ELSE SET "ct=LZX" & DEL /F /A "!targetFile!.zip.cab"
	)
	ECHO(
	ECHO(Keeping !ct!
)
DEL /F /A "!targetFile!.ddf"
IF %fileType% EQU 0 ENDLOCAL & EXIT /B 0

:: Generate self-extracting script
ECHO(
CertUtil /encode "!targetFile!.cab" "!targetFile!.b64"
>"!targetFile!.b64.cmd" (
	ECHO(:: !cabName!.b64.cmd [target\folder] [noExpand]
	ECHO(:: ErrorLevel: 0=success, 1=no CertUtil install Administration Tools
	ECHO(@ECHO OFF
	ECHO(FOR /F "tokens=3-12" %%%%A IN ^('Expand'^) DO ^(
	ECHO(	SETLOCAL EnableExtensions EnableDelayedExpansion
	ECHO(	SET "fn=!cabName!.cab"
	ECHO(	IF "%%~1"=="" ^(SET "tf=%%~dp0"^) ELSE SET "tf=%%~1" ^& MD "%%~1"
	ECHO(	IF "^!tf:~-1^!"=="\" SET "tf=^!tf:~0,-1^!"
	ECHO(	CertUtil -decode -f "%%~f0" "^!TEMP^!\^!fn^!"
	ECHO(	IF ^^!ERRORLEVEL^^!==9009 ENDLOCAL ^& EXIT /B 1
	ECHO(	IF "%%~2"=="" ^(SET /A "ev=%%%%J%%%%I%%%%H%%%%G%%%%F%%%%E%%%%D%%%%C%%%%B%%%%A"
	ECHO(		IF ^^!ev^^! GEQ 6 ^(Expand -R "^!TEMP^!\^!fn^!" -F:* "^!tf^!"
	ECHO(		^) ELSE Extract /Y /E /L "^!tf^!" "^!TEMP^!\^!fn^!"
	ECHO(		IF ^^!ERRORLEVEL^^!==9009 START "" /MIN /WAIT Extrac32 /Y /E /L "^!tf^!" "^!TEMP^!\^!fn^!"
	ECHO(		IF ^^!ERRORLEVEL^^!==0 DEL /F /A "^!TEMP^!\^!fn^!"^)
	ECHO(	MOVE /Y "^!TEMP^!\^!fn^!" "^!tf^!" ^& RD "%%~1"
	ECHO(	ENDLOCAL ^& EXIT /B 0^) ^>NUL 2^>^&1
	TYPE "!targetFile!.b64"
)
DEL /F /A "!targetFile!.cab" "!targetFile!.b64"
ENDLOCAL & EXIT /B 0

:bin2enc_help
FOR /F "usebackq tokens=* delims=:" %%A IN ("%SELF%") DO IF "%%A" NEQ "" (ECHO(%%A) ELSE ENDLOCAL & EXIT /B 0
OriginalPost:
I was intending on writing a small script to automate the MakeCab+CertUtil+Expand Base64 file embedding process, which makes me wonder...
How openly available are all these executables? Hopefully at least as far back as WInXP?
Has anyone already written a fancy automation script for this (multiple files, individual file extraction, etc)?
Are these the best windows-included utilities for this operation?

Incidentally, I already know about Carlos' excellent BHX VBscript utility, I just like to keep my batch files more winNT-batch-y. ^_^

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 24 Sep 2018 20:38
by ShadowThief
I wrote something a while ago that works reasonably well, but I'm sure somebody will find some edge cases that I missed.

Code: Select all

::------------------------------------------------------------------------------
:: Generates a cabinet file from a provided folder that contains all other files
:: and subfolders that were inside of the main directory. From there, gets
:: converted to base64 and put in a self-extracting batch script.
::
:: Requires certutil.
::
:: Taken from http://www.dostips.com/forum/viewtopic.php?t=1977#p8751
::------------------------------------------------------------------------------
@echo off
setlocal enabledelayedexpansion

:: If no folder was provided, exit immediately
if "%~1"=="" exit /b
if not exist "%~1\" exit /b

set "config_file=%~dp0\directives.ddf"
set "target_file=%~dp0\%~n1.cab"

pushd "%~1"
:: Add the header to the cabinet config file
>%config_file% (
	echo ; Generated %date% %time%
	echo .Option Explicit
	echo .Set SourceDir="%~1"
	echo .Set DiskDirectoryTemplate="%~dp0"
	echo .Set CabinetNameTemplate="%~n1.cab"
	echo .Set Cabinet=ON
	echo .Set Compress=ON
	echo .Set CompressionType=MSZIP
	echo .Set DestinationDir="%~n1"
)

:: Add the list of files to the cabinet config file
echo(Processing all files in %cd%
for /f "delims=" %%A in ('dir /a:-d /b') do (
	>>%config_file% echo "%%A"
)

:: Add the subdirectories and all related files to the cabinet config file
for /f "delims=" %%A in ('dir /a:d /b /s') do (
	set "inner_target=%%A"
	set "inner_target=!inner_target:%~1\=!"
	echo(Processing all files in !inner_target!
	>>%config_file% echo .Set SourceDir="%%A"
	>>%config_file% echo .Set DestinationDir="!inner_target!"
	for /f "delims=" %%B in ('dir /a:-d /b "%%A"') do (
		>>%config_file% echo "%%B"
	)
)
popd

:: Generate the cabinet file
makecab /f %config_file%
del %~dp0\directives.ddf
del %~dp1\setup.rpt
del %~dp1\setup.inf

:: Generate the extraction script
>>%~n1_setup.bat (
	echo @echo off
	echo certutil -decode "%%~0" "%%~n0.cab"
	echo mkdir "%%~n0" 2^>nul
	echo expand "%%~n0.cab" -f:* "%%~n0"
	echo del "%%~n0.cab"
)
certutil -encode %target_file% %target_file%.b64
>>%~n1_setup.bat (
	echo exit /b
	type %target_file%.b64
)
del %target_file%.b64

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 25 Sep 2018 07:24
by Aacini
Perhaps you may be interested in BinToBat.bat conversion program...

Antonio

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 27 Sep 2018 18:47
by CirothUngol
Thanks for the great sample script! I've played with it for awhile and determined that MakeCab.exe probably isn't the best solution for multiple files/folders. Not only is it difficult and archaic (but manageable), but I fear that it may be incompatible with many modern features like unicode filenames, large filesizes, and deep recursive folders. I really have only ever used this method to embed a single executable in my batch scripts (BG.EXE), so no biggie.

I will continue to use CertUtil assuming Certificate Services to be available on most machines.

...and thanks for the link to bin2bat, neat script! I was just looking for a simple embedding process that needn't call other scripting languages (Jscript or VBscript).

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 27 Sep 2018 18:56
by ShadowThief
Yeah, when I use the script, I generally stick everything in one folder and then just pass that folder as an argument instead of trying to handle multiple files as multiple parameters simultaneously.

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 09 Oct 2018 23:46
by CirothUngol
OK, I think I've finally gotten the MakeCab part correct (cantankerous to say the least). Here's the final script I've begun using:

Code: Select all

::------------------------------------------------------------------------------
:: bin2enc.cmd source\folder[\file] [destination\folder]
:: 
:: Generates CAB or self-extracting batch script from a source file or folder
:: using MakeCab + Expand for compression and CertUtil for Base64 encoding.
::------------------------------------------------------------------------------
::
@ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET "CompressionType=MSZIP" ' faster compression
SET "CompressionType=LZX"   ' smaller files
SET "keepSrcDir=0"          ' 0=don't, 1=retain source folder in archive
SET "fileType=0"            ' 0=archive.cab, 1=self-extracting.cmd

:: exit if no input, input doesn't exist, or file count=0
SET count=0
IF NOT EXIST "%~1" ECHO(No files found& EXIT /B 1
FOR /F %%A IN ('DIR /S "%~1" 2^>NUL ^| FIND /I "File(s)"') DO SET /A "count=%%A" 2>NUL
IF %count% EQU 0 ECHO(No files found& EXIT /B 1

:: get full path to source/target
FOR %%A IN ("%~1") DO SET "source=%%~fA" & SET "sPath=%%~pnxA"
FOR %%A IN ("%~2") DO SET "target=%%~fA"
IF NOT DEFINED target SET "target=%~dp0"
MD "%target%" 2>NUL
IF %keepSrcDir% EQU 0 (SET "destDir=") ELSE SET "destDir=%~nx1"

:: Add the header to the cabinet config file
SET "targetFile=%target%\%~nx1"
>"%targetFile%.ddf" (
	ECHO(; Generated on %DATE% at %TIME: =0%
	ECHO(.New Cabinet
	ECHO(.Set CabinetNameTemplate="%~nx1.cab"
	ECHO(.Set DiskDirectoryTemplate="%target%"
	ECHO(.Set GenerateInf=OFF
	ECHO(.Set Cabinet=ON
	ECHO(.Set Compress=ON
	ECHO(.Set UniqueFiles=ON
	ECHO(.Set CompressionType=%CompressionType%
	ECHO(.Set MaxDiskSize=1215751680
	ECHO(.Set RptFileName=NUL
	ECHO(.Set InfFileName=NUL
	ECHO(.Set MaxErrors=1
)

:: if source is folder add files iteratively
IF EXIST "%source%\" (
	FOR /D /R "%source%" %%A IN (*) DO (
		SET "tDir=%%~pnxA"
		SET "dDir=%destDir%!tDir:%sPath%=!"
		ECHO(.Set DestinationDir=!dDir!>>"%targetFile%.ddf"
		FOR %%B IN ("%%A\*") DO ECHO("%%~fB"  /inf=no>>"%targetFile%.ddf"
	)
	ECHO(.Set DestinationDir=%destDir%>>"%targetFile%.ddf"
	FOR %%A IN ("%source%\*") DO ECHO("%%~fA"  /inf=no>>"%targetFile%.ddf"
) ELSE ECHO("%source%"  /inf=no>>"%targetFile%.ddf"
MakeCab /F "%targetFile%.ddf"
DEL /F /A "%targetFile%.ddf"
IF %fileType% EQU 0 EXIT /B 0

:: Generate self-extracting script
ECHO(
CertUtil /encode "%targetFile%.cab" "%targetFile%.b64"
>"%targetFile%.b64.cmd" (
	ECHO(@ECHO OFF
	ECHO(CertUtil -decode -f "%%~f0" "%%TEMP%%\%~nx1.cab" ^>NUL
	ECHO(MD "%%~1" 2^>NUL
	ECHO(IF "%%~1"=="" ^( Expand -r "%%TEMP%%\%~nx1.cab" -F:* "%%CD%%" ^>NUL
	ECHO(^) ELSE Expand -r "%%TEMP%%\%~nx1.cab" -F:* "%%~1" ^>NUL
	ECHO(DEL /F /A "%%TEMP%%\%~nx1.cab"
	ECHO(EXIT /B 0
	TYPE "%targetFile%.b64"
)
DEL /F /A "%targetFile%.cab" "%targetFile%.b64"
EXIT /B 0
It can be used to make cabinet files or self-extracting scripts from a single file or a whole folder, I just drag and drop. ^_^
If anyone knows of some better settings to use for MakeCab, please share. It seems to be handling everything correctly, but it can always be more robust.

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 10 Oct 2018 06:50
by carlos
@CirothUngol maybe you can add option CompressionMemory=21 to the LZX method, it can save more bytes.
Maybe is possible dinamycally create two files, one using MSZIP and other using LZX and choose the one that produce less bytes.
Or even if you provide old diamond.exe viewtopic.php?t=7791
you can create the cab using QUANTUM compression that can be better for small files. https://ss64.org/viewtopic.php?id=1597

Re: MakeCab, CertUtil, & Expand... best file embedding?

Posted: 10 Oct 2018 23:59
by CirothUngol
Great suggestions, and thanks for the info. Sad MakeCab doesn't support QUANTUM, but I'll just keep it simple. ^_^
Updated the script and posted it to the OP.

Re: bin2enc.cmd - Native .CAB & Self-Extracting Scripts

Posted: 20 Oct 2018 20:25
by CirothUngol
Updated the script, should work nicely on earlier WinNT systems now. Couldn't find a clean link to the MS Cabinet SDK (cabsdk) for Extract.exe, but if you look around you can find it. Otherwise, just endure Extrac32's minimized pop-up. ^_^
Please post if you try it an have any issues.

Re: bin2enc.cmd - Native .CAB & Self-Extracting Scripts

Posted: 23 Oct 2018 20:37
by carlos
Hello @CirothUngol:

I try process a single file:
bin2enc.cmd image.png

But it not works.

I replaced this:

Code: Select all

SET count=0
IF NOT EXIST "%~1" GOTO :bin2enc_help
FOR /F %%A IN ('DIR /S "%~f1" 2^>NUL ^| FIND /I "File(s)"') DO SET /A "count=%%A" 2>NUL
IF %count% EQU 0 GOTO :bin2enc_help
with:

Code: Select all

SET count=0
IF NOT EXIST "%~f1" GOTO :bin2enc_help
FOR /F %%A IN ('DIR /B "%~f1" 2^>NUL') DO SET /A "count+=1"
IF %count% EQU 0 GOTO :bin2enc_help
And now it can process single file.
Tha lines count a single file as 1 or a directory.
The File(s) word is only compatible for windows on english.
With that adjust I can test okay your nice script.

Re: bin2enc.cmd - Native .CAB & Self-Extracting Scripts

Posted: 29 Oct 2018 11:24
by CirothUngol
Thanks for the correction Carlos, I always forget about the non-english aspect of batch files. Corrected the line in the first post, didn't alter the version number.