|
|
|
@ -0,0 +1,680 @@
|
|
|
|
|
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^ |
|
|
|
|
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @' |
|
|
|
|
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \ |
|
|
|
|
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \ |
|
|
|
|
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" + |
|
|
|
|
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' |
|
|
|
|
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section. |
|
|
|
|
: shebang line is not required on unix or windows and will reduce functionality and/or portability. |
|
|
|
|
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care. |
|
|
|
|
@GOTO :skip_perl_pod_start ^; |
|
|
|
|
=begin excludeperl |
|
|
|
|
: skip_perl_pod_start |
|
|
|
|
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \ |
|
|
|
|
: { |
|
|
|
|
@REM ############################################################################################################################ |
|
|
|
|
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe) |
|
|
|
|
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained. |
|
|
|
|
@REM ############################################################################################################################ |
|
|
|
|
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate. |
|
|
|
|
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system |
|
|
|
|
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath> |
|
|
|
|
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used) |
|
|
|
|
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called. |
|
|
|
|
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only. |
|
|
|
|
@SETLOCAL EnableExtensions EnableDelayedExpansion |
|
|
|
|
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'" |
|
|
|
|
@SET "shells[10]=pwsh" |
|
|
|
|
@SET "shells[11]=sh" |
|
|
|
|
@set "shells[12]=bash" |
|
|
|
|
@SET "shells[13]=tclsh" |
|
|
|
|
@SET "shells[14]=perl" |
|
|
|
|
: <nextshell> |
|
|
|
|
@SET "nextshell=13" |
|
|
|
|
: </nextshell> |
|
|
|
|
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable). |
|
|
|
|
: <asadmin> |
|
|
|
|
@SET "asadmin=0" |
|
|
|
|
: </asadmin> |
|
|
|
|
@REM nextshell set to index for validshells .eg 10 for pwsh |
|
|
|
|
@REM @ECHO nextshell is %nextshell% |
|
|
|
|
@SET "selected=!shells[%nextshell%]!" |
|
|
|
|
@REM @ECHO selected %selected% |
|
|
|
|
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%" |
|
|
|
|
@REM @ECHO keyremoved %keyRemoved% |
|
|
|
|
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available |
|
|
|
|
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
@REM -- cmd/batch file section (ignored on unix but should be left in place) |
|
|
|
|
@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary) |
|
|
|
|
@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone. |
|
|
|
|
@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 |
|
|
|
|
@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly. |
|
|
|
|
@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133 |
|
|
|
|
@REM ############################################################################################################################ |
|
|
|
|
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway) |
|
|
|
|
@REM -- Even something as simple as adding or removing an @REM |
|
|
|
|
@REM -- From within punkshell - use: |
|
|
|
|
@REM -- deck scriptwrap.checkfile <filepath> |
|
|
|
|
@REM -- to check your templates or final wrapped scripts for byte boundary issues |
|
|
|
|
@REM -- It will report any labels that are on boundaries |
|
|
|
|
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets. |
|
|
|
|
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended. |
|
|
|
|
@REM -- Alternatively, as you should do anyway - test the final script on windows |
|
|
|
|
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations. |
|
|
|
|
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here. |
|
|
|
|
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label |
|
|
|
|
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries. |
|
|
|
|
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided |
|
|
|
|
@REM ############################################################################################################################ |
|
|
|
|
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections |
|
|
|
|
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
@SET "winpath=%~dp0" |
|
|
|
|
@SET "fname=%~nx0" |
|
|
|
|
@REM @ECHO fname %fname% |
|
|
|
|
@REM @ECHO winpath %winpath% |
|
|
|
|
@REM @ECHO commandlineascalled %0 |
|
|
|
|
@REM @ECHO commandlineresolved %~f0 |
|
|
|
|
@CALL :getNormalizedScriptTail nftail |
|
|
|
|
@REM @ECHO normalizedscripttail %nftail% |
|
|
|
|
@CALL :getFileTail %0 clinetail |
|
|
|
|
@REM @ECHO clinetail %clinetail% |
|
|
|
|
@CALL :stringToUpper %~nx0 capscripttail |
|
|
|
|
@REM @ECHO capscriptname: %capscripttail% |
|
|
|
|
|
|
|
|
|
@IF "%nftail%"=="%capscripttail%" ( |
|
|
|
|
@ECHO forcing asadmin=1 due to file name on filesystem being uppercase |
|
|
|
|
@SET "asadmin=1" |
|
|
|
|
) else ( |
|
|
|
|
@CALL :stringToUpper %clinetail% capcmdlinetail |
|
|
|
|
@REM @ECHO capcmdlinetail !capcmdlinetail! |
|
|
|
|
IF "%clinetail%"=="!capcmdlinetail!" ( |
|
|
|
|
@ECHO forcing asadmin=1 due to cmdline scriptname in uppercase |
|
|
|
|
@set "asadmin=1" |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs" |
|
|
|
|
@SET arglist=%* |
|
|
|
|
@SET "qstrippedargs=args%arglist%" |
|
|
|
|
@SET "qstrippedargs=%qstrippedargs:"=%" |
|
|
|
|
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( |
|
|
|
|
GOTO :gotPrivileges |
|
|
|
|
) |
|
|
|
|
@IF !asadmin!==1 ( |
|
|
|
|
net file 1>NUL 2>NUL |
|
|
|
|
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) |
|
|
|
|
) |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@REM |
|
|
|
|
@GOTO skip_privileges |
|
|
|
|
:getPrivileges |
|
|
|
|
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges ) |
|
|
|
|
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" |
|
|
|
|
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" |
|
|
|
|
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" |
|
|
|
|
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" |
|
|
|
|
@ECHO Next >> "%vbsGetPrivileges%" |
|
|
|
|
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%" |
|
|
|
|
@ECHO Launching script in new windows due to administrator elevation |
|
|
|
|
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:gotPrivileges |
|
|
|
|
@REM setlocal & pushd . |
|
|
|
|
@PUSHD . |
|
|
|
|
@cd /d %~dp0 |
|
|
|
|
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( |
|
|
|
|
@DEL "%vbsGetPrivileges%" 1>nul 2>nul |
|
|
|
|
@SET arglist=%arglist:~14% |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
:skip_privileges |
|
|
|
|
@SET need_ps1=0 |
|
|
|
|
@REM we want the ps1 to exist even if the nextshell isn't powershell |
|
|
|
|
@if not exist "%~dp0%~n0.ps1" ( |
|
|
|
|
@SET need_ps1=1 |
|
|
|
|
) ELSE ( |
|
|
|
|
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different |
|
|
|
|
@REM @ECHO "files same" |
|
|
|
|
@SET need_ps1=0 |
|
|
|
|
) |
|
|
|
|
@GOTO :pscontinue |
|
|
|
|
:different |
|
|
|
|
@REM @ECHO "files differ" |
|
|
|
|
@SET need_ps1=1 |
|
|
|
|
:pscontinue |
|
|
|
|
@IF !need_ps1!==1 ( |
|
|
|
|
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL |
|
|
|
|
) |
|
|
|
|
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /? |
|
|
|
|
@IF "!shells[%nextshell%]!"=="pwsh" ( |
|
|
|
|
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time |
|
|
|
|
REM test availability of preferred option of powershell7+ pwsh |
|
|
|
|
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL |
|
|
|
|
SET pwshtest_exitcode=!errorlevel! |
|
|
|
|
REM ECHO pwshtest_exitcode !pwshtest_exitcode! |
|
|
|
|
REM fallback to powershell if pwsh failed |
|
|
|
|
IF !pwshtest_exitcode!==0 ( |
|
|
|
|
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% |
|
|
|
|
SET task_exitcode=!errorlevel! |
|
|
|
|
) ELSE ( |
|
|
|
|
REM CALL powershell -nop -nol -c write-host powershell-found |
|
|
|
|
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* |
|
|
|
|
powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist% |
|
|
|
|
SET task_exitcode=!errorlevel! |
|
|
|
|
) |
|
|
|
|
) ELSE ( |
|
|
|
|
IF "!shells[%nextshell%]!"=="bash" ( |
|
|
|
|
CALL :getWslPath %winpath% wslpath |
|
|
|
|
REM ECHO wslfullpath "!wslpath!%fname%" |
|
|
|
|
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% |
|
|
|
|
SET task_exitcode=!errorlevel! |
|
|
|
|
) ELSE ( |
|
|
|
|
REM probably tclsh or sh |
|
|
|
|
IF NOT "x%keyRemoved%"=="x%validshells%" ( |
|
|
|
|
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl |
|
|
|
|
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx |
|
|
|
|
!shells[%nextshell%]! "%~dp0%fname%" %arglist% |
|
|
|
|
SET task_exitcode=!errorlevel! |
|
|
|
|
) ELSE ( |
|
|
|
|
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells% |
|
|
|
|
SET task_exitcode=66 |
|
|
|
|
@REM boundary padding |
|
|
|
|
@REM boundary padding |
|
|
|
|
GOTO :exit_multishell |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@REM batch file library functions |
|
|
|
|
@REM boundary padding |
|
|
|
|
@GOTO :endlib |
|
|
|
|
|
|
|
|
|
:getWslPath |
|
|
|
|
@SETLOCAL |
|
|
|
|
@SET "_path=%~p1" |
|
|
|
|
@SET "name=%~nx1" |
|
|
|
|
@SET "drive=%~d1" |
|
|
|
|
@SET "rtrn=%~2" |
|
|
|
|
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%" |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@if "%~2" neq "" ( |
|
|
|
|
SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
ECHO %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:getFileTail |
|
|
|
|
@REM return tail of file without any normalization e.g c:/punkshell/bin/Punk.cmd returns Punk.cmd even if file is punk.cmd |
|
|
|
|
@REM we can't use things such as %~nx1 as it can change capitalisation |
|
|
|
|
@REM This function is designed explicitly to preserve capitalisation |
|
|
|
|
@REM accepts full paths with either / or \ as delimiters - or |
|
|
|
|
@SETLOCAL |
|
|
|
|
@SET "rtrn=%~2" |
|
|
|
|
@SET "arg=%~1" |
|
|
|
|
@REM @SET "result=%_arg:*/=%" |
|
|
|
|
@REM @SET "result=%~1" |
|
|
|
|
@SET LF=^ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
: The above 2 empty lines are important. Don't remove |
|
|
|
|
@CALL :stringContains "!arg!" "\" hasBackSlash |
|
|
|
|
@IF "!hasBackslash!"=="true" ( |
|
|
|
|
@for %%A in ("!LF!") do @( |
|
|
|
|
@FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B" |
|
|
|
|
) |
|
|
|
|
) ELSE ( |
|
|
|
|
@CALL :stringContains "!arg!" "/" hasForwardSlash |
|
|
|
|
@IF "!hasForwardSlash!"=="true" ( |
|
|
|
|
@FOR %%A in ("!LF!") do @( |
|
|
|
|
@FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B" |
|
|
|
|
) |
|
|
|
|
) ELSE ( |
|
|
|
|
@set "result=%arg%" |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@if "%~2" neq "" ( |
|
|
|
|
@SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
@ECHO %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
@REM boundary padding |
|
|
|
|
@REM boundary padding |
|
|
|
|
:getNormalizedScriptTail |
|
|
|
|
@SETLOCAL |
|
|
|
|
@SET "result=%~nx0" |
|
|
|
|
@SET "rtrn=%~1" |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@IF "%~1" neq "" ( |
|
|
|
|
@SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
@ECHO %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:getNormalizedFileTailFromPath |
|
|
|
|
@REM warn via echo, and do not set return variable if path not found |
|
|
|
|
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized' |
|
|
|
|
@REM boundary padding |
|
|
|
|
@REM boundary padding |
|
|
|
|
@REM boundary padding |
|
|
|
|
@REM boundary padding |
|
|
|
|
@SETLOCAL |
|
|
|
|
@CALL :stringContains %~1 "\" hasBackSlash |
|
|
|
|
@CALL :stringContains %~1 "/" hasForwardSlash |
|
|
|
|
@IF "%hasBackslash%-%hasForwardslash%"=="false-false" ( |
|
|
|
|
@SET "P=%cd%%~1" |
|
|
|
|
@CALL :getNormalizedFileTailFromPath "!P!" ftail2 |
|
|
|
|
@SET "result=!ftail2!" |
|
|
|
|
) else ( |
|
|
|
|
@IF EXIST "%~1" ( |
|
|
|
|
@SET "result=%~nx1" |
|
|
|
|
) else ( |
|
|
|
|
@ECHO error getNormalizedFileTailFromPath file not found: %~1 |
|
|
|
|
@EXIT /B 1 |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@SET "rtrn=%~2" |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@IF "%~2" neq "" ( |
|
|
|
|
SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
@ECHO getNormalizedFileTailFromPath %1 result: %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:stringContains |
|
|
|
|
@REM usage: @CALL:stringContains string needle returnvarname |
|
|
|
|
@SETLOCAL |
|
|
|
|
@SET "rtrn=%~3" |
|
|
|
|
@SET "string=%~1" |
|
|
|
|
@SET "needle=%~2" |
|
|
|
|
@IF "!string:%needle%=!"=="!string!" @( |
|
|
|
|
@SET "result=false" |
|
|
|
|
) ELSE ( |
|
|
|
|
@SET "result=true" |
|
|
|
|
) |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@IF "%~3" neq "" ( |
|
|
|
|
@SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
@ECHO stringContains %string% %needle% result: %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:stringToUpper |
|
|
|
|
@SETLOCAL |
|
|
|
|
@SET "rtrn=%~2" |
|
|
|
|
@SET "string=%~1" |
|
|
|
|
@SET "capstring=%~1" |
|
|
|
|
@FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @( |
|
|
|
|
@SET "capstring=!capstring:%%A=%%A!" |
|
|
|
|
) |
|
|
|
|
@SET "result=!capstring!" |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@IF "%~2" neq "" ( |
|
|
|
|
@SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
@ECHO stringToUpper %string% result: %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:isNumeric |
|
|
|
|
@SETLOCAL |
|
|
|
|
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" |
|
|
|
|
@IF defined notnumeric ( |
|
|
|
|
@SET "result=false" |
|
|
|
|
) else ( |
|
|
|
|
@SET "result=true" |
|
|
|
|
) |
|
|
|
|
@SET "rtrn=%~2" |
|
|
|
|
@ENDLOCAL & ( |
|
|
|
|
@IF "%~2" neq "" ( |
|
|
|
|
@SET "%rtrn%=%result%" |
|
|
|
|
) ELSE ( |
|
|
|
|
@ECHO %result% |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
@EXIT /B |
|
|
|
|
|
|
|
|
|
:endlib |
|
|
|
|
: \ |
|
|
|
|
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell |
|
|
|
|
@GOTO :exit_multishell |
|
|
|
|
# } |
|
|
|
|
# -*- tcl -*- |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
# -- tcl script section |
|
|
|
|
# -- This is a punk multishell file |
|
|
|
|
# -- Primary payload target is Tcl, with sh,bash,powershell as helpers |
|
|
|
|
# -- but it may equally be used with any of these being the primary script. |
|
|
|
|
# -- It is tuned to run when called as a batch file, a tcl script a sh/bash script or a pwsh/powershell script |
|
|
|
|
# -- i.e it is a polyglot file. |
|
|
|
|
# -- The specific layout including some lines that appear just as comments is quite sensitive to change. |
|
|
|
|
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline. |
|
|
|
|
# -- e.g ./filename.polypunk.cmd in sh or bash |
|
|
|
|
# -- e.g tclsh filename.cmd |
|
|
|
|
# -- |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore |
|
|
|
|
Hide :exit_multishell;Hide {<#};Hide '@ |
|
|
|
|
namespace eval ::punk::multishell { |
|
|
|
|
set last_script_root [file dirname [file normalize ${argv0}/__]] |
|
|
|
|
set last_script [file dirname [file normalize [info script]/__]] |
|
|
|
|
if {[info exists argv0] && |
|
|
|
|
$last_script eq $last_script_root |
|
|
|
|
} { |
|
|
|
|
set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode |
|
|
|
|
} else { |
|
|
|
|
set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return. |
|
|
|
|
} |
|
|
|
|
if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} { |
|
|
|
|
proc ::punk::multishell::is_main {{script_name {}}} { |
|
|
|
|
if {$script_name eq ""} { |
|
|
|
|
set script_name [file dirname [file normalize [info script]/--]] |
|
|
|
|
} |
|
|
|
|
if {![info exists ::punk::multishell::is_main($script_name)]} { |
|
|
|
|
#e.g a .dll or something else unanticipated |
|
|
|
|
puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting" |
|
|
|
|
puts stderr "Info: script_root: [file dirname [file normalize ${argv0}/__]]" |
|
|
|
|
return 0 |
|
|
|
|
} |
|
|
|
|
return [set ::punk::multishell::is_main($script_name)] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload |
|
|
|
|
#puts "script : [info script]" |
|
|
|
|
#puts "argcount : $::argc" |
|
|
|
|
#puts "argvalues: $::argv" |
|
|
|
|
#puts "argv0 : $::argv0" |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#<tcl-pre-launch-subprocess> |
|
|
|
|
#</tcl-pre-launch-subprocess> |
|
|
|
|
|
|
|
|
|
#<tcl-launch-subprocess> |
|
|
|
|
#</tcl-launch-subproces> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#<tcl-post-launch-subprocess> |
|
|
|
|
#</tcl-post-launch-subproces> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- |
|
|
|
|
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. |
|
|
|
|
# -- If the multishell script is modified to have Tcl below the Tcl Payload section, |
|
|
|
|
# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below. |
|
|
|
|
# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below |
|
|
|
|
# -- but the sh/bash 'then' and 'fi' would also need to be uncommented. |
|
|
|
|
# -- This facility left in place for experiments on whether configuration payloads etc can be appended |
|
|
|
|
# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells |
|
|
|
|
# -- can be made to ignore/cope with such data. |
|
|
|
|
if {[::punk::multishell::is_main]} { |
|
|
|
|
exit 0 |
|
|
|
|
} else { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload |
|
|
|
|
# end hide from unix shells \ |
|
|
|
|
HEREDOC1B_HIDE_FROM_BASH_AND_SH |
|
|
|
|
# sh/bash \ |
|
|
|
|
shift && set -- "${@:1:$#-1}" |
|
|
|
|
#------------------------------------------------------ |
|
|
|
|
# -- This if block only needed if Tcl didn't exit or return above. |
|
|
|
|
if false==false # else { |
|
|
|
|
then |
|
|
|
|
: # |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
# -- sh/bash script section |
|
|
|
|
# -- leave as is if all that is required is launching the Tcl payload" |
|
|
|
|
# -- |
|
|
|
|
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default |
|
|
|
|
# -- adjust the %nextshell% value above |
|
|
|
|
# -- if sh/bash scripting needs to run on windows too. |
|
|
|
|
# -- |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload |
|
|
|
|
exitcode=0 |
|
|
|
|
#printf "start of bash or sh code" |
|
|
|
|
|
|
|
|
|
#<shell-pre-launch-subprocess> |
|
|
|
|
#</shell-pre-launch-subprocess> |
|
|
|
|
|
|
|
|
|
# -- --- --- --- --- --- --- --- |
|
|
|
|
#<shell-launch-subprocess> |
|
|
|
|
#-- sh/bash launches Tcl here instead of shebang line at top |
|
|
|
|
#-- use exec to use exitcode (if any) directly from the tcl script |
|
|
|
|
#exec /usr/bin/env tclsh "$0" "$@" |
|
|
|
|
#-- alternative - can run sh/bash script after the tcl call. |
|
|
|
|
/usr/bin/env tclsh "$0" "$@" |
|
|
|
|
exitcode=$? |
|
|
|
|
#echo "sh/bash reporting tcl exitcode: ${exitcode}" |
|
|
|
|
#-- override exitcode example |
|
|
|
|
#exit 66 |
|
|
|
|
#</shell-launch-subprocess> |
|
|
|
|
# -- --- --- --- --- --- --- --- |
|
|
|
|
|
|
|
|
|
#<shell-post-launch-subprocess> |
|
|
|
|
#</shell-post-launch-subproces> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#printf "sh/bash done \n" |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload |
|
|
|
|
#------------------------------------------------------ |
|
|
|
|
fi |
|
|
|
|
exit ${exitcode} |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
# -- Perl script section |
|
|
|
|
# -- leave the script below as is, if all that is required is launching the Tcl payload" |
|
|
|
|
# -- |
|
|
|
|
# -- Note that perl script isn't called by default when simply running this script by name |
|
|
|
|
# -- adjust the nextshell value at the top of the script to point to perl |
|
|
|
|
# -- |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
=cut |
|
|
|
|
#!/user/bin/perl |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload |
|
|
|
|
my $exit_code = 0; |
|
|
|
|
#use ExtUtils::Installed; |
|
|
|
|
#my $installed = ExtUtils::Installed->new(); |
|
|
|
|
#my @modules = $installed->modules(); |
|
|
|
|
#print "Modules:\n"; |
|
|
|
|
#foreach my $m (@modules) { |
|
|
|
|
# print "$m\n"; |
|
|
|
|
#} |
|
|
|
|
# -- --- --- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
my $scriptname = $0; |
|
|
|
|
print "perl $scriptname\n"; |
|
|
|
|
my $i =1; |
|
|
|
|
foreach my $a(@ARGV) { |
|
|
|
|
print "Arg # $i: $a\n"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#<perl-pre-launch-subprocess> |
|
|
|
|
#</perl-pre-launch-subprocess> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- --- --- --- --- --- --- --- |
|
|
|
|
#<perl-launch-subprocess> |
|
|
|
|
$exit_code=system("tclsh", $scriptname, @ARGV); |
|
|
|
|
#print "perl reporting tcl exitcode: $exit_code"; |
|
|
|
|
#</perl-launch-subprocess> |
|
|
|
|
# -- --- --- --- --- --- --- --- |
|
|
|
|
|
|
|
|
|
#<perl-post-launch-subprocess> |
|
|
|
|
#</perl-post-launch-subprocess> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload |
|
|
|
|
exit $exit_code; |
|
|
|
|
__END__ |
|
|
|
|
|
|
|
|
|
# end hide sh/bash/perl block from Tcl |
|
|
|
|
# This comment with closing brace should stay in place whether if commented or not } |
|
|
|
|
#------------------------------------------------------ |
|
|
|
|
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above |
|
|
|
|
if 0 { |
|
|
|
|
: end heredoc1 - end hide from powershell \ |
|
|
|
|
'@ |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
# -- powershell/pwsh section |
|
|
|
|
# -- Do not edit if current file is the .ps1 |
|
|
|
|
# -- Edit the corresponding .cmd and it will autocopy |
|
|
|
|
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above |
|
|
|
|
# -- custom script should generally go below the begin_powershell_payload line |
|
|
|
|
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### |
|
|
|
|
function GetScriptName { $myInvocation.ScriptName } |
|
|
|
|
$scriptname = GetScriptName |
|
|
|
|
function GetDynamicParamDictionary { |
|
|
|
|
[CmdletBinding()] |
|
|
|
|
param( |
|
|
|
|
[Parameter(ValueFromPipeline=$true, Mandatory=$true)] |
|
|
|
|
[string] $CommandName |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
begin { |
|
|
|
|
# Get a list of params that should be ignored (they're common to all advanced functions) |
|
|
|
|
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | |
|
|
|
|
Get-Member -MemberType Properties | |
|
|
|
|
Select-Object -ExpandProperty Name |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
process { |
|
|
|
|
# Create the dictionary that this scriptblock will return: |
|
|
|
|
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary |
|
|
|
|
|
|
|
|
|
# Convert to object array and get rid of Common params: |
|
|
|
|
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() | |
|
|
|
|
Where-Object { $CommonParameterNames -notcontains $_.Key } | |
|
|
|
|
ForEach-Object { |
|
|
|
|
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( |
|
|
|
|
$_.Key, |
|
|
|
|
$_.Value.ParameterType, |
|
|
|
|
$_.Value.Attributes |
|
|
|
|
) |
|
|
|
|
$DynParamDictionary.Add($_.Key, $DynamicParameter) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Return the dynamic parameters |
|
|
|
|
return $DynParamDictionary |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
# GetDynamicParamDictionary |
|
|
|
|
# - This can make it easier to share a single set of param definitions between functions |
|
|
|
|
# - sample usage |
|
|
|
|
#function ParameterDefinitions { |
|
|
|
|
# param( |
|
|
|
|
# [Parameter(Mandatory)][string] $myargument |
|
|
|
|
# ) |
|
|
|
|
#} |
|
|
|
|
#function psmain { |
|
|
|
|
# [CmdletBinding()] |
|
|
|
|
# param() |
|
|
|
|
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions } |
|
|
|
|
# process { |
|
|
|
|
# #called once with $PSBoundParameters dictionary |
|
|
|
|
# #can be used to validate arguments, or set a simpler variable name for access |
|
|
|
|
# switch ($PSBoundParameters.keys) { |
|
|
|
|
# 'myargumentname' { |
|
|
|
|
# Set-Variable -Name $_ -Value $PSBoundParameters."$_" |
|
|
|
|
# } |
|
|
|
|
# #... |
|
|
|
|
# } |
|
|
|
|
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) { |
|
|
|
|
# #... |
|
|
|
|
# } |
|
|
|
|
# } |
|
|
|
|
# end { |
|
|
|
|
# #Main function logic |
|
|
|
|
# Write-Host "myargumentname value is: $myargumentname" |
|
|
|
|
# #myotherfunction @PSBoundParameters |
|
|
|
|
# } |
|
|
|
|
#} |
|
|
|
|
#psmain @args |
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload |
|
|
|
|
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host |
|
|
|
|
#"Script Name : {0}" -f $scriptname | write-host |
|
|
|
|
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host |
|
|
|
|
#"powershell args : {0}" -f ($args -join ", ") | write-host |
|
|
|
|
# -- --- --- --- |
|
|
|
|
|
|
|
|
|
#<powershell-pre-launch-subprocess> |
|
|
|
|
#</powershell-pre-launch-subprocess> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- --- --- --- --- --- --- --- |
|
|
|
|
#<powershell-launch-subprocess> |
|
|
|
|
tclsh $scriptname $args |
|
|
|
|
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host |
|
|
|
|
#</powershell-launch-subprocess> |
|
|
|
|
# -- --- --- --- --- --- --- --- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#<powershell-post-launch-subprocess> |
|
|
|
|
#</powershell-post-launch-subprocess> |
|
|
|
|
|
|
|
|
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload |
|
|
|
|
Exit $LASTEXITCODE |
|
|
|
|
# heredoc2 for powershell to ignore block below |
|
|
|
|
$1 = @' |
|
|
|
|
' |
|
|
|
|
: comment end hide powershell-block from Tcl \ |
|
|
|
|
# This comment with closing brace should stay in place whether 'if' commented or not } |
|
|
|
|
: multishell doubled-up cmd exit label - return exitcode |
|
|
|
|
:exit_multishell |
|
|
|
|
:exit_multishell |
|
|
|
|
: \ |
|
|
|
|
@REM @ECHO exitcode: !task_exitcode! |
|
|
|
|
: \ |
|
|
|
|
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit) |
|
|
|
|
: \ |
|
|
|
|
@EXIT /B !task_exitcode! |
|
|
|
|
# cmd has exited |
|
|
|
|
: comment end heredoc2 \ |
|
|
|
|
'@ |
|
|
|
|
<# |
|
|
|
|
# id:tailblock0 |
|
|
|
|
# -- powershell multiline comment |
|
|
|
|
#> |
|
|
|
|
<# |
|
|
|
|
no script engine should try to run me |
|
|
|
|
# id:tailblock1 |
|
|
|
|
# <ctrl-z> |
|
|
|
|
|
|
|
|
|
# </ctrl-z> |
|
|
|
|
# -- unreachable by tcl directly if ctrl-z character is in the <ctrl-z> section above. (but file can be read and split on \x1A) |
|
|
|
|
# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data |
|
|
|
|
# -- so for example a plain text tar archive could cause problems depending on the content. |
|
|
|
|
# -- final line in file must be the powershell multiline comment terminator or other data it can handle. |
|
|
|
|
# -- e.g plain # comment lines will work too |
|
|
|
|
# -- (for example a powershell digital signature is a # commented block of data at the end of the file) |
|
|
|
|
#> |
|
|
|
|
|
|
|
|
|
|