Skip to content

Instantly share code, notes, and snippets.

@mklement0
Last active December 6, 2022 01:35
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mklement0/738f5291fdb252bdc80e13b47c0cdef5 to your computer and use it in GitHub Desktop.
Save mklement0/738f5291fdb252bdc80e13b47c0cdef5 to your computer and use it in GitHub Desktop.
wrapper.cmd - a wrapper batch-file template for executing embedded PowerShell and/or VBScript/JScript code
@echo off
setLocal enableDelayedExpansion
:: === ADAPTING THIS TEMPLATE (for help, see bottom or invoke with "help") ===
:: * Step 1 of 3: CLONE THIS BATCH FILE and give it a name of your choice.
:: * Step 2 of 3: Set the TARGET LANGUAGE on the next line.
:: One of: "ps1" "vbs" "js" (PowerShell, VBScript, JScript)
:: OR: "all" (runs ALL embedded snippets, in sequence)
:: NOTE: To add support for a new language, search for "NEW LANGUAGE"
:: below and follow the instructions there.
set "WRAPPER_LANG=ps1"
:: * Step 3 of 3: PASTE THE SOURCE CODE TO EMBED BELOW,
:: below the appropriate "::begin {lang-id}" line.
:: E.g., for target language "ps1" (PowerShell), find "::begin ps1"
:: For improved performance, you may remove any unused language
:: blocks between the "=== BEGIN/END: CUSTOM EMBEDDED CODE BLOCKS ===" markers.
:: ===
:: If invoked with original filename, set a flag to effect running in demo mode.
if "%~nx0" == "wrapper.cmd" set WRAPPER_DEMO_MODE=1
:: Create a randomly named temp. dir and proceed to label :unpack.
:: Note: The purpose of the loop is simply to retry on accidental name collision.
for /L %%I in (1,1,10) do @(
set "dir=!tmp!\%~n0-tmp-!RANDOM!"
mkdir "!dir!" 2>nul && goto unpack
)
echo %~nx0: Failed to find unique name for temporary dir: !dir!>&2
goto :eof
:: Unpack (extract) the embedded code snippets from this script itself
:: - including entry point WRAPPER_main.cmd - into distinct files in the temp. dir.,
:: then invoke main.cmd to start execution and, finally, remove the temp. dir.
:: Note: The contents of this script is read *line by line*, which is likely
:: slow with large embedded code snippets.
:unpack
set script=NUL
:: A '::begin {name}' block can have an optional *3rd* argument that is the
:: placeholder char. for lines to consider *empty* and pass through as such (used with "::begin help -" below)
:: For technical reasons, empty lines are discarded during extration.
set empty=
:: The following loop sets environment variables named WRAPPER_{block-name}
:: with "." instances replaced with "_" whose values are the full filenames
:: of the temporary files that the respective blocks are being extracted to.
:: E.g., block "[::begin ]main.cmd" becomes env. var. "WRAPPER_main_cmd" with
:: a value such as "C:\Users\jdoe\AppData\Local\Temp\wrapper-tmp-28002\WRAPPER.main.cmd"
:: !! IMPORTANT: The `skip={line-no}` part is hard-coded to skip past the top part of
:: !! this script during extraction. When modifying this script, be sure
:: !! to update {line-no} to the line number BEFORE the first `::begin ` line below.
for /f "usebackq skip=74 delims=" %%L in ("%~f0") do (
for /f "tokens=1-3" %%A in ("%%L") do (
if "%%A" == "::begin" (
set name=WRAPPER.%%B
set script="!dir!\!name!"
set !name:.=_!=!script!
set empty=%%C
) else if "%%A" == "::paste" (
type "!dir!\WRAPPER.%%B" >>!script!
) else if "%%A" == "!empty!" (
echo.>>!script!
) else echo.%%L>>!script!
)
)
:: Call the extracted entry-point batch file, relaying all parameters.
:: On completion, remove the temp. dir.
call !WRAPPER_main_cmd! %* & rmdir /s /q "%dir%"
goto :eof
::Define the command-line prefixes for the supported languages, used in main.cmd.
:: === ADDING SUPPORT FOR A NEW LANGUAGE ===
:: * Add a `{lang-id}={cmdline-prefix}` line below (the script filename operand will be appended on invocation)
:: Choose the language's customary filename extension (without the ".") as the {lang-ID}; e.g., "py"
:: * Add a new `::begin {lang-id}` block below, where the embedded code will go.
:: * Clone an existing `:{lang-id}` label block inside the `::begin main.cmd` block, and adapt it.
:: IMPORTANT: !! IF YOU MODIFY ANY PART OF THIS SCRIPT *ABOVE* THIS LINE, YOU MUST UPDATE THE `skip={line-no}` EXPRESSION ABOVE WITH *THIS* LINE'S NUMBER.
::begin invoke.txt
js cscript.exe /Nologo
ps1 powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File
vbs cscript.exe /Nologo
::begin main.cmd
:: == THE ENTRY POINT FOR EXECUTION AFTER EXTRACTION, via a temp. file ==
:: Read the language-specific invocation command-line prefixes.
for /f "usebackq tokens=1*" %%A in (!WRAPPER_invoke_txt!) do set "%%A=%%B"
set break=goto :eof
:: When invoked with the original filename, operate in demo mode.
if %WRAPPER_DEMO_MODE%.==1. (
for %%P in (help ps1 vbs js all) do if "%1" == "%%P" goto %%P
echo Unrecognized or missing language ID: %1>&2
type %WRAPPER_usage_txt%>&2
) else (
REM Invoke the fixed target language set at the top.
goto :%WRAPPER_LANG%
)
%break%
:help
type %WRAPPER_help_txt%
%break%
:all
set break=
:: NOTE: Every supported language must have a :{lang-id} label block below.
:ps1
%ps1% %WRAPPER_ps1% %*
%break%
:vbs
%vbs% %WRAPPER_vbs% %*
:js
%js% %WRAPPER_js% %*
%break%
::begin dummy :: === BEGIN: CUSTOM EMBEDDED CODE BLOCKS ===
::
:: NOTE:
:: All '::begin' sections below start a language-specific block of embedded
:: source code or a helper block, and each block ends *implicitly* with
: the subsequent '::begin' or the end of the file.
::
:: If you use snippets for more than one language, you can refer to the
:: temp. file created for the snippet of a different language via an
:: environment variable named "WRAPPER_{lang-ext}"; e.g., to reference the
:: temp. file with the embeded JS code from PowerShell:
:: $env:WRAPPER_js
::
:: In the blocks below You can include the text from other blocks with a
:: "::paste {block-name}" line, which, for instance, can be used to include
:: helper code that you want available by default for a given language.
:: The technique is used in the '::begin js' block below and also
:: in the '::begin help.txt' section to include the 'usage.txt' section.
:: Example helper-code block for JS
::begin helper.js
// Make working with WSH collections a little less tedious.
function items(coll) {
var result = [];
for (var i = 0; i < coll.length; ++i) {
result.push(coll.item(i));
}
return result;
}
::begin ps1
"Hello from WRAPPER.ps1. Args: $($args -join ', ')"
# Demonstration of calling another snippet from this file.
# $env:WRAPPER_js expands to the full filename of the temporary file that
# contains the JS code embedded in the "::begin js" block below.
cscript.exe /Nologo $env:WRAPPER_js 'Called from PowerShell' @args
::begin vbs
WScript.Echo "Hello from WRAPPER.vbs. Args:"
for each arg in WScript.Arguments
WScript.Echo arg
next
::begin js
// The following line includes the text from '::begin helper.js' block above, which defines the items() function.
::paste helper.js
WScript.Echo('Hello from WRAPPER.js. Args: ' + items(WScript.Arguments).join(', '));
::begin dummy :: === END: CUSTOM EMBEDDED CODE BLOCKS ===
::begin usage.txt
Usage: wrapper.cmd ps1|vbs|js|all [ARGS]
wrapper.cmd help
::begin help.txt -
::paste usage.txt
-
This batch file is a TEMPLATE showing a way to EMBED SEVERAL WINDOWS
SCRIPTING LANGUAGES INSIDE A SINGLE SCRIPT.
This is primarily useful for PowerShell and VBS/JS scripts: PowerShell scripts
cannot be *directly* executed from cmd.exe or File Explorer, and VBS/JS scripts
may not execute as *console* applications. By embedding such code in an
instance of this template, the resulting batch file can be invoked directly
and is guaranteed to execute the embedded code as a console application.
-
To see this in action, invoke this template with one of the language
identifiers listed above, which executes embedded sample code; passing "all"
executes all embedded source code, across languages, in sequence.
-
TO MAKE USE OF THIS TEMPLATE, YOU NEED TO CLONE IT, replace the sample script
fragments inside it with your own, and set the target language. See the top of
this batch file for step-by-step instructions, including how to add support
for additional languages.
-
This batch file works by unpacking itself into separate scripts inside a
temporary folder, defining environment variables allowing for easy invocation
of each of these, via a wrapper batch file that relays all original arguments
and remove the temp. folder on completion. All environment variable names have
prefix "WRAPPER_".
-
ORIGINAL AND MAIN AUTHOR:
Stephen Thomas <flabdablet@fastmail.fm>
ADAPTED BY (from the version obtained on 3 Mar 2018 from
http://flabdablet.nfshost.com/windows-scripts/glue.cmd):
Michael Klement <mklement0@gmail.com>
-
This is free software. Do whatever you like with it except hold me accountable
for any grief it causes you.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment