diff --git a/bin/getzig.cmd b/bin/getzig.cmd
index 1108691d..2cb8900b 100644
--- a/bin/getzig.cmd
+++ b/bin/getzig.cmd
@@ -1,4 +1,4 @@
-: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh zsh/bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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 + \
@@ -13,7 +13,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: 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, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
+@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, zsh, bash, (sh diversion) 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 -------------------------------------------------------------------------------------------------------------------------------
@@ -847,16 +847,6 @@ namespace eval ::punk::multishell {
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#
-#
-#
-
-#
-#
-
-
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@@ -877,28 +867,61 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
-# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
-# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
-# zsh diversion \
-# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
-# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
-echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
-# non-bash-like diversion \
-if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
-# sh/bash (or zsh?) \
-shift && set -- "${@:1:$((${#@}-1))}"
-# \
-#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+echo "shell from ps: $ps_shellname"
# \
echo "args: $@"
+# ------------------------------------------------------------------------------
+# -- This if block wraps posix sh diversion section - only needed if Tcl didn't exit or return above.
+if false==false # else {
+ then
+ : #
+
+# https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c0 (MIT lic)
+# https://stackoverflow.com/questions/63864755/remove-last-argument-in-shell-script-posix
+# posix compliant pop
+pop() {
+ __pop_n=$(($1 - ${2:-1}))
+ if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
+ POP_EXPR='set -- "${@:1:'$__pop_n'}"'
+ elif [ $__pop_n -ge 500 ]; then
+ POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')"
+ else
+ __pop_index=0
+ __pop_arguments=""
+ while [ $__pop_index -lt $__pop_n ]; do
+ __pop_index=$((__pop_index+1))
+ __pop_arguments="$__pop_arguments \"\${$__pop_index}\""
+ done
+ POP_EXPR="set -- $__pop_arguments"
+ fi
+}
+# ------------------------------------------------------------------------------
+
+# non-bash-like posix diversion \
+if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
+ shift
+ pop $#
+ eval "$POP_EXPR"
+ echo "divert to bash $0 $@"
+
+ /usr/bin/env bash "$0" "$@"
+ exit $?
+fi
+# close false==false block
+fi
+# close tcl wrap }
# ------------------------------------------------------
-# -- This if block only needed if Tcl didn't exit or return above.
+# -- This if block wraps whole zsh/bash and perl sections - only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
+
+
+# zsh/bash \
+shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
@@ -910,7 +933,7 @@ if false==false # else {
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
-if [[ "$plat" = "Linux"* ]]; then
+if [[ "$plat" == "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
@@ -922,11 +945,11 @@ elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
-elif [[ "$plat" = "MINGW32"* ]]; then
+elif [[ "$plat" == "MINGW32"* ]]; then
os="win32"
-elif [[ "$plat" = "MINGW64"* ]]; then
+elif [[ "$plat" == "MINGW64"* ]]; then
os="win32"
-elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
@@ -972,34 +995,34 @@ for ln in "${arr_oslines[@]}"; do
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
- echo "nextshellpath: $nextshellpath"
+ # echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
- echo "nextshelltype: $nextshelltype"
+ # echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
- echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
- #echo "sh/bash reporting exitcode: ${exitcode}"
+ #echo "zsh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
- #echo "bash payload"
+ #echo "zsh/bash payload"
:
fi
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
-#printf "start of bash or sh code"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin zsh Payload
+#printf "start of bash or zsh code"
#
#mkdir -p ./zig
@@ -1020,29 +1043,9 @@ echo "Unimplemented: Download from ${full_uristring} and extract manually"
#
-#
-#
-
-# -- --- --- --- --- --- --- ---
-#
-#-- 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
-#
-# -- --- --- --- --- --- --- ---
-
-#
-#
-
-#printf "sh/bash done \n"
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
+#printf "zsh/bash done \n"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end zsh Payload
#------------------------------------------------------
fi
exit ${exitcode}
@@ -1078,7 +1081,6 @@ print "os $os\n";
# -- --- ---
-
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
@@ -1088,21 +1090,11 @@ foreach my $a(@ARGV) {
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#
-#
-#
-
-
-
# -- --- --- --- --- --- --- ---
-#
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
-#
# -- --- --- --- --- --- --- ---
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
@@ -1563,20 +1555,6 @@ if ($mirrors_response.StatusCode -eq 200) {
#
-#
-#
-
-
-# -- --- --- --- --- --- --- ---
-#
-#tclsh $scriptname $args
-#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
-#
-# -- --- --- --- --- --- --- ---
-
-
-#
-#
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE
diff --git a/bin/runtime.cmd b/bin/runtime.cmd
index 13874262..be23cbb8 100755
--- a/bin/runtime.cmd
+++ b/bin/runtime.cmd
@@ -1,4 +1,4 @@
-: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh zsh/bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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 + \
@@ -13,7 +13,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: 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, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
+@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, zsh, bash, (sh diversion) 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 -------------------------------------------------------------------------------------------------------------------------------
@@ -847,16 +847,6 @@ namespace eval ::punk::multishell {
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#
-#
-#
-
-#
-#
-
-
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@@ -877,28 +867,61 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
-# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
-# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
-# zsh diversion \
-# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
-# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
-echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
-# non-bash-like diversion \
-if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
-# sh/bash (or zsh?) \
-shift && set -- "${@:1:$((${#@}-1))}"
-# \
-#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+echo "shell from ps: $ps_shellname"
# \
echo "args: $@"
+# ------------------------------------------------------------------------------
+# -- This if block wraps posix sh diversion section - only needed if Tcl didn't exit or return above.
+if false==false # else {
+ then
+ : #
+
+# https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c0 (MIT lic)
+# https://stackoverflow.com/questions/63864755/remove-last-argument-in-shell-script-posix
+# posix compliant pop
+pop() {
+ __pop_n=$(($1 - ${2:-1}))
+ if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
+ POP_EXPR='set -- "${@:1:'$__pop_n'}"'
+ elif [ $__pop_n -ge 500 ]; then
+ POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')"
+ else
+ __pop_index=0
+ __pop_arguments=""
+ while [ $__pop_index -lt $__pop_n ]; do
+ __pop_index=$((__pop_index+1))
+ __pop_arguments="$__pop_arguments \"\${$__pop_index}\""
+ done
+ POP_EXPR="set -- $__pop_arguments"
+ fi
+}
+# ------------------------------------------------------------------------------
+
+# non-bash-like posix diversion \
+if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
+ shift
+ pop $#
+ eval "$POP_EXPR"
+ echo "divert to bash $0 $@"
+
+ /usr/bin/env bash "$0" "$@"
+ exit $?
+fi
+# close false==false block
+fi
+# close tcl wrap }
# ------------------------------------------------------
-# -- This if block only needed if Tcl didn't exit or return above.
+# -- This if block wraps whole zsh/bash and perl sections - only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
+
+
+# zsh/bash \
+shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
@@ -910,7 +933,7 @@ if false==false # else {
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
-if [[ "$plat" = "Linux"* ]]; then
+if [[ "$plat" == "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
@@ -922,11 +945,11 @@ elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
-elif [[ "$plat" = "MINGW32"* ]]; then
+elif [[ "$plat" == "MINGW32"* ]]; then
os="win32"
-elif [[ "$plat" = "MINGW64"* ]]; then
+elif [[ "$plat" == "MINGW64"* ]]; then
os="win32"
-elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
@@ -972,34 +995,34 @@ for ln in "${arr_oslines[@]}"; do
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
- echo "nextshellpath: $nextshellpath"
+ # echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
- echo "nextshelltype: $nextshelltype"
+ # echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
- echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
- #echo "sh/bash reporting exitcode: ${exitcode}"
+ #echo "zsh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
- #echo "bash payload"
+ #echo "zsh/bash payload"
:
fi
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
-#printf "start of bash or sh code"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin zsh Payload
+#printf "start of bash or zsh code"
#
@@ -1144,29 +1167,9 @@ esac
#
-#
-#
-
-# -- --- --- --- --- --- --- ---
-#
-#-- 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
-#
-# -- --- --- --- --- --- --- ---
-
-#
-#
-
-#printf "sh/bash done \n"
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
+#printf "zsh/bash done \n"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end zsh Payload
#------------------------------------------------------
fi
exit ${exitcode}
@@ -1202,7 +1205,6 @@ print "os $os\n";
# -- --- ---
-
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
@@ -1212,21 +1214,11 @@ foreach my $a(@ARGV) {
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#
-#
-#
-
-
-
# -- --- --- --- --- --- --- ---
-#
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
-#
# -- --- --- --- --- --- --- ---
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
@@ -1751,7 +1743,7 @@ function psmain {
Write-host "-----------------------------------------------------------------------"
Write-host "Runtimes for $arch"
Write-host "Local $archfolder"
- Write-host "Remote $$archurl"
+ Write-host "Remote $archurl"
Write-host "-----------------------------------------------------------------------"
Write-host "Local Remote"
Write-host "-----------------------------------------------------------------------"
@@ -1830,20 +1822,6 @@ exit 0
#
-#
-#
-
-
-# -- --- --- --- --- --- --- ---
-#
-#tclsh $scriptname $args
-#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
-#
-# -- --- --- --- --- --- --- ---
-
-
-#
-#
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE
diff --git a/getpunk.cmd b/getpunk.cmd
index e1bc3be5..858fd64f 100755
--- a/getpunk.cmd
+++ b/getpunk.cmd
@@ -1,4 +1,4 @@
-: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh zsh/bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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 + \
@@ -13,7 +13,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: 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, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
+@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, zsh, bash, (sh diversion) 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 -------------------------------------------------------------------------------------------------------------------------------
@@ -266,7 +266,8 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
- call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell %=
+ @rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
+ call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@@ -303,6 +304,7 @@ SETLOCAL EnableDelayedExpansion
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh 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
+ REM when run without cmd.exe - pwsh will receive the semicolon (for cmd.exe unquoted semicolon and comma are separators that aren't seen in positional arguments)
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
@@ -310,18 +312,18 @@ SETLOCAL EnableDelayedExpansion
IF !pwshtest_exitcode!==0 (
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
- cmd /c pwsh -nop -nol -ExecutionPolicy bypass -c "%scriptrootname%.ps1" !newcommandline!
+ cmd /c pwsh -nop -nol -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
- cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
+ cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
- cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
+ cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@@ -373,6 +375,7 @@ endlocal & set "%~3=%rtn%"
exit /b
%= ---------------------------------------------------------------------- =%
+@REM padding
:buildcmdline cmdlinevar paramvar wrapA wrapB
%= quoting for cmd.exe /c pwsh -nop !args! =%
@SETLOCAL EnableDelayedExpansion
@@ -650,6 +653,8 @@ do if not defined param1 set %%~"param1=%2%%~"
@REM boundary padding
@REM boundary padding
@REM boundary padding
+@REM boundary padding
+@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@@ -842,16 +847,6 @@ namespace eval ::punk::multishell {
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#
-#
-#
-
-#
-#
-
-
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@@ -872,28 +867,61 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
-# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
-# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
-# zsh diversion \
-# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
-# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
-echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
-# non-bash-like diversion \
-if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
-# sh/bash (or zsh?) \
-shift && set -- "${@:1:$((${#@}-1))}"
-# \
-#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+echo "shell from ps: $ps_shellname"
# \
echo "args: $@"
+# ------------------------------------------------------------------------------
+# -- This if block wraps posix sh diversion section - only needed if Tcl didn't exit or return above.
+if false==false # else {
+ then
+ : #
+
+# https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c0 (MIT lic)
+# https://stackoverflow.com/questions/63864755/remove-last-argument-in-shell-script-posix
+# posix compliant pop
+pop() {
+ __pop_n=$(($1 - ${2:-1}))
+ if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
+ POP_EXPR='set -- "${@:1:'$__pop_n'}"'
+ elif [ $__pop_n -ge 500 ]; then
+ POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')"
+ else
+ __pop_index=0
+ __pop_arguments=""
+ while [ $__pop_index -lt $__pop_n ]; do
+ __pop_index=$((__pop_index+1))
+ __pop_arguments="$__pop_arguments \"\${$__pop_index}\""
+ done
+ POP_EXPR="set -- $__pop_arguments"
+ fi
+}
+# ------------------------------------------------------------------------------
+
+# non-bash-like posix diversion \
+if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
+ shift
+ pop $#
+ eval "$POP_EXPR"
+ echo "divert to bash $0 $@"
+
+ /usr/bin/env bash "$0" "$@"
+ exit $?
+fi
+# close false==false block
+fi
+# close tcl wrap }
# ------------------------------------------------------
-# -- This if block only needed if Tcl didn't exit or return above.
+# -- This if block wraps whole zsh/bash and perl sections - only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
+
+
+# zsh/bash \
+shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
@@ -905,7 +933,7 @@ if false==false # else {
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
-if [[ "$plat" = "Linux"* ]]; then
+if [[ "$plat" == "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
@@ -917,11 +945,11 @@ elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
-elif [[ "$plat" = "MINGW32"* ]]; then
+elif [[ "$plat" == "MINGW32"* ]]; then
os="win32"
-elif [[ "$plat" = "MINGW64"* ]]; then
+elif [[ "$plat" == "MINGW64"* ]]; then
os="win32"
-elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
@@ -967,34 +995,34 @@ for ln in "${arr_oslines[@]}"; do
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
- echo "nextshellpath: $nextshellpath"
+ # echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
- echo "nextshelltype: $nextshelltype"
+ # echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
- echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
- #echo "sh/bash reporting exitcode: ${exitcode}"
+ #echo "zsh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
- #echo "bash payload"
+ #echo "zsh/bash payload"
:
fi
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
-#printf "start of bash or sh code"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin zsh Payload
+#printf "start of bash or zsh code"
#
@@ -1090,29 +1118,9 @@ cd $launchdir #restore original CWD
#
-#
-#
-
-# -- --- --- --- --- --- --- ---
-#
-#-- 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
-#
-# -- --- --- --- --- --- --- ---
-
-#
-#
-
-#printf "sh/bash done \n"
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
+#printf "zsh/bash done \n"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end zsh Payload
#------------------------------------------------------
fi
exit ${exitcode}
@@ -1148,7 +1156,6 @@ print "os $os\n";
# -- --- ---
-
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
@@ -1158,21 +1165,11 @@ foreach my $a(@ARGV) {
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#
-#
-#
-
-
-
# -- --- --- --- --- --- --- ---
-#
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
-#
# -- --- --- --- --- --- --- ---
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
@@ -1540,20 +1537,6 @@ write-host "`e[92m getpunk done `e[m"
#
-#
-#
-
-
-# -- --- --- --- --- --- --- ---
-#
-#tclsh $scriptname $args
-#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
-#
-# -- --- --- --- --- --- --- ---
-
-
-#
-#
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE
diff --git a/getpunk.cmd.lastrun b/getpunk.cmd.lastrun
new file mode 100644
index 00000000..e1bc3be5
--- /dev/null
+++ b/getpunk.cmd.lastrun
@@ -0,0 +1,1596 @@
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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, (some 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 -------------------------------------------------------------------------------------------------------------------------------
+@rem return from endlocal macro - courtesy of jeb
+@rem This allows return of values containing special characters from subroutines
+@rem https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/8257951#8257951
+@rem -------------------------------------------------------------------------------------------------------------------------------
+@setlocal DisableDelayedExpansion
+@echo off
+%= 2 blank lines after next are required =%
+set LF=^
+
+
+set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
+%= I use EDE for EnableDelayeExpansion and DDE for DisableDelayedExpansion =%
+set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
+ setlocal EnableDelayedExpansion%\n%
+ %= Take all variable names into the varName array =%%\n%
+ set varName_count=0%\n%
+ for %%C in (!args!) do set "varName[!varName_count!]=%%~C" ^& set /a varName_count+=1%\n%
+ %= Build one variable with a list of set statements for each variable delimited by newlines =%%\n%
+ %= The lists looks like --> set result1=myContent\n"set result1=myContent1"\nset result2=content2\nset result2=content2\n =%%\n%
+ %= Each result exists two times, the first for the case returning to DDE, the second for EDE =%%\n%
+ %= The correct line will be detected by the (missing) enclosing quotes =%%\n%
+ set "retContent=1!LF!"%\n%
+ for /L %%n in (0 1 !varName_count!) do (%\n%
+ for /F "delims=" %%C in ("!varName[%%n]!") DO (%\n%
+ set "content=!%%C!"%\n%
+ set "retContent=!retContent!"set !varName[%%n]!=!content!"!LF!"%\n%
+ if defined content (%\n%
+ %= This complex block is only for replacing '!' with '^!' =%%\n%
+ %= First replacing '"'->'""q' '^'->'^^' =%%\n%
+ set ^"content_EDE=!content:"=""q!"%\n%
+ set "content_EDE=!content_EDE:^=^^!"%\n%
+ %= Now it's poosible to use CALL SET and replace '!'->'""e!' =%%\n%
+ call set "content_EDE=%%content_EDE:^!=""e^!%%"%\n%
+ %= Now it's possible to replace '""e' to '^', this is effectivly '!' -> '^!' =%%\n%
+ set "content_EDE=!content_EDE:""e=^!"%\n%
+ %= Now restore the quotes =%%\n%
+ set ^"content_EDE=!content_EDE:""q="!"%\n%
+ ) ELSE set "content_EDE="%\n%
+ set "retContent=!retContent!set "!varName[%%n]!=!content_EDE!"!LF!"%\n%
+ )%\n%
+ )%\n%
+ %= Now return all variables from retContent over the barrier =%%\n%
+ for /F "delims=" %%V in ("!retContent!") DO (%\n%
+ %= Only the first line can contain a single 1 =%%\n%
+ if "%%V"=="1" (%\n%
+ %= We need to call endlocal twice, as there is one more setlocal in the macro itself =%%\n%
+ endlocal%\n%
+ endlocal%\n%
+ ) ELSE (%\n%
+ %= This is true in EDE =%%\n%
+ if "!"=="" (%\n%
+ if %%V==%%~V (%\n%
+ %%V !%\n%
+ )%\n%
+ ) ELSE IF not %%V==%%~V (%\n%
+ %%~V%\n%
+ )%\n%
+ )%\n%
+ )%\n%
+ ) else set args="
+
+@rem -------------------------------------------------------------------------------------------------------------------------------
+@SETLOCAL EnableExtensions EnableDelayedExpansion
+@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
+@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
+@REM e.g from within a running punkshell: dev scriptwrap.multishell -outputfolder
+@REM Call with sh, bash, perl, or tclsh. (powershell untested on unix)
+@REM Due to lack of shebang (#! line) Unix-like systems will hopefully default to a flavour of sh that can divert to bash 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.
+@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
+@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
+@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
+@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
+@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
+@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
+@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
+@REM Supporting more explicit oses than those listed may also require script padding adjustment
+: <>
+@SET "nextshellpath[win32___________]=powershell -nop -nol -ExecutionPolicy ByPass -File______________"
+@SET "nextshelltype[win32___________]=powershell______"
+@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[dragonflybsd____]=bash____________"
+@SET "nextshellpath[freebsd_________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[freebsd_________]=bash____________"
+@SET "nextshellpath[netbsd__________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[netbsd__________]=bash____________"
+@SET "nextshellpath[linux___________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[linux___________]=bash____________"
+@SET "nextshellpath[macosx__________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[macosx__________]=bash____________"
+@SET "nextshellpath[other___________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[other___________]=bash____________"
+: <>
+@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).
+: <>
+@SET "asadmin=0"
+: <>
+@SET "selected_shelltype=%nextshelltype[win32___________]%"
+@REM @ECHO selected_shelltype %selected_shelltype%
+@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
+@REM @ECHO selected_shelltype_trimmed %selected_shelltype_trimmed%
+@SET "selected_shellpath=%nextshellpath[win32___________]%"
+@CALL :stringTrimTrailingUnderscores %selected_shellpath% selected_shellpath_trimmed
+@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%"
+@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" %= e.g c:\punkshell\bin\ %=
+@SET "fname=%~nx0"
+@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
+@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 padding
+@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 window 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 "!selected_shelltype_trimmed!"=="none" (
+ SET selected_shelltype_trimmed=pwsh
+)
+
+
+
+
+@set argCount=30
+@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
+@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
+@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
+
+@set tmpfile_base=%TEMP%\punkbatch_params
+@call :getUniqueFile %tmpfile_base% ".txt" paramfile
+@echo %paramfile%
+
+%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
+@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
+@rem outer loop required to redirect all rem lines at once to file
+@for %%x in (1) do @(
+ @for /L %%f in (1,1,%argCount%) do @(
+ @set "argnum=%%~nf"
+ @set "a1=%%1"
+ @rem @set "argname=%%!argnum!"
+ @rem @echo argname: !argname!
+ @call :rem_output !argnum! !a1!
+ @shift
+ )
+) > %paramfile%
+@echo off
+
+@set "newcommandline= "
+
+@(set target=cmd_pwsh)
+@if "%target%"=="cmd_pwsh" (
+ @for /F "delims=" %%L in (%paramfile%) do @(
+ SETLOCAL DisableDelayedExpansion
+ set "param=%%L"
+ @REM @echo ######### %%L
+ @rem call :buildcmdline newcommandline param "{" "}"
+ call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell %=
+ @rem @echo .
+ )
+) ELSE (
+ @for /F "delims=" %%L in (%paramfile%) do @(
+ SETLOCAL DisableDelayedExpansion
+ set "param=%%L"
+ call :buildcmdline newcommandline param
+ )
+)
+@REM padding
+SETLOCAL EnableDelayedExpansion
+
+@echo off
+@IF EXIST %paramfile% (
+ @DEL /F /Q %paramfile%
+)
+@IF EXIST %paramfile% (
+ echo failed to delete %paramfile%
+ cat %paramfile%
+)
+
+
+
+@REM @SET "squoted_args="
+@REM @for %%a in (%*) do @(
+@REM set "v=%%a"
+@REM set "v=!v:'=''!"
+@REM SET "squoted_args=!squoted_args!'!v!' "
+@REM )
+@REM @SET "squoted_args=%squoted_args:~0,-1%"
+@REM @ECHO %squoted_args%
+
+
+@IF "!selected_shelltype_trimmed!"=="pwsh" (
+ REM pwsh 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 2>NUL; 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 (
+ @rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
+ @rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
+ cmd /c pwsh -nop -nol -ExecutionPolicy bypass -c "%scriptrootname%.ps1" !newcommandline!
+ SET task_exitcode=!errorlevel!
+ ) ELSE (
+ REM TODO prompt user with option to call script to install pwsh using winget
+ @rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
+ cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
+ SET task_exitcode=!errorlevel!
+ )
+) ELSE (
+ IF "!selected_shelltype_trimmed!"=="powershell" (
+ @rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
+ cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
+ SET task_exitcode=!errorlevel!
+ ) ELSE (
+ IF "!selected_shelltype_trimmed!"=="wslbash" (
+ CALL :getWslPath %winpath% wslpath
+ REM ECHO wslfullpath "!wslpath!%fname%"
+ %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
+ SET task_exitcode=!errorlevel!
+ ) ELSE (
+ REM perl or tcl or sh or bash
+ IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
+ 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
+ REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
+ @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
+ %selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
+ ) ELSE (
+ ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
+ SET task_exitcode=66
+ @REM boundary padding
+ GOTO :exit_multishell
+ )
+ )
+ )
+)
+@REM batch file library functions
+
+@GOTO :endlib
+
+@REM padding
+@REM padding
+@REM padding
+
+%= ---------------------------------------------------------------------- =%
+@rem courtesy of dbenham
+:: Example usage
+@rem call :getUniqueFile "d:\test\myFile" ".txt" myFile
+@rem echo myFile="%myFile%"
+
+:getUniqueFile baseName extension rtnVar
+setlocal
+:getUniqueFileLoop
+for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
+if exist "%rtn%" (
+ goto :getUniqueFileLoop
+) else (
+ 2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
+)
+endlocal & set "%~3=%rtn%"
+exit /b
+%= ---------------------------------------------------------------------- =%
+
+:buildcmdline cmdlinevar paramvar wrapA wrapB
+ %= quoting for cmd.exe /c pwsh -nop !args! =%
+ @SETLOCAL EnableDelayedExpansion
+
+ @REM @echo =====================
+ set "pval=!%~2:*#=!"
+ set "pval=!pval:~0,-2!"
+ @REM set "pval=!pval:~0,-1!"
+ set "wrapa=%~3"
+ set "wrapb=%~4"
+
+ @call :strlen pval slen
+ @rem @echo strlen: !slen!
+ if "!slen!"=="0" (
+ goto :eof
+ )
+ @set /A n = !slen! - 1
+ @(set str=)
+ @set "dq=""
+ @set "bang=^!"
+ @(set carat=^^)
+ @for /l %%i in (0,1,!n!) do @(
+ (set c=!pval:~%%i,1!)
+ if "!c!"=="|" (
+ set "ch=^^!pval:~%%i,1!"
+ ) ELSE IF "!c!"=="(" (
+ set "ch=^("
+ ) ELSE if "!c!"==")" (
+ set "ch=^)"
+ ) ELSE if "!c!"=="&" (
+ set "ch=^^&"
+ ) ELSE if "!c!"=="!dq!" (
+ set "ch=^""
+ ) ELSE if "!c!"=="!bang!" (
+ @rem - double caret - first for initial parsing, second to ensure remains escaped during delayed expansion phase
+ @rem - REVIEW
+ set "ch=^^!bang!"
+ ) ELSE if "!c!"=="^carat" (
+ set "ch=^^^^"
+ ) ELSE if "!c!"=="'" (
+ set "ch=''"
+ ) ELSE (
+ set "ch=!c!"
+ )
+ @rem @echo - !ch!
+ set "str=!str!!ch!"
+ )
+ echo +++++ %~1 = !%1! !str!
+
+ set "%~1=!%1! !wrapa!!str!!wrapb!"
+
+ @rem old method of return - failes to preserve exclamation marks
+ @rem for /f "delims=" %%A in (""!str!"") do endlocal & set "%~1=!%1! '%%~A'"
+ @rem macro method of endlocal return - preserving !val!
+ @echo off
+ %endlocal% %~1
+
+ @exit /b
+
+:rem_output
+ @rem ---------------------------------------------
+ @rem rem_output is called for each n in the number of args we process - as we don't have a non-destructive way to count args whilst accepting special chars
+ @rem we therefore need a way to stop processing on the last received arg so we don't write argCount entries to param.txt if less are received
+ @rem see 'disappearing quotes' technique
+ @rem https://stackoverflow.com/questions/4643376/how-to-split-double-quoted-line-into-multiple-lines-in-windows-batch-file/4645113#4645113
+ @rem and
+ @rem https://groups.google.com/g/alt.msdos.batch.nt/c/J71F17Bve9Y (sponge belly)
+ @echo off
+ setlocal enableextensions disabledelayedexpansion
+ set "param1=%~2"
+ rem do must not be indented
+ for %%^" in ("") ^
+do if not defined param1 set %%~"param1=%2%%~"
+ if not defined param1 goto :eof
+ endlocal
+ @rem ---------------------------------------------
+
+ @PROMPT @
+ @echo on
+ rem %1 #%2#
+@exit /b
+
+@REM courtesy of: https://stackoverflow.com/users/463115/jeb
+:strlen stringVar returnVar
+@(
+ setlocal EnableDelayedExpansion
+ @SET "rtrn=%~2"
+ (set^ tmp=!%~1!)
+ @rem @echo jjjjj !tmp!
+ @if defined tmp (
+ set "len=1"
+ @for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do @(
+ @if "!tmp:~%%P,1!" NEQ "" (
+ set /a "len+=%%P"
+ set "tmp=!tmp:~%%P!"
+ )
+ )
+ ) ELSE (
+ set len=0
+ )
+)
+@(
+ endlocal
+ @IF "%~2" neq "" (
+ @SET "%rtrn%=%len%"
+ ) ELSE (
+ @ECHO :strlen result: %len%
+ )
+ exit /b
+)
+
+
+:getWslPath
+@SETLOCAL
+ @SET "_path=%~p1"
+ @SET "name=%~nx1"
+ @SET "drive=%~d1"
+ @SET "rtrn=%~2"
+ @REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters
+ @CALL :stringToLower %drive ldrive
+ @SET "result=/mnt/%ldrive:~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
+
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+: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'
+@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
+
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+
+: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
+@REM boundary padding
+@REM boundary padding
+:stringToUpper strvar returnvar
+@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
+:stringToLower
+@SETLOCAL
+ @SET "rtrn=%~2"
+ @SET "string=%~1"
+ @SET "retstring=%~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 "retstring=!retstring:%%A=%%A!"
+ )
+ @SET "result=!retstring!"
+@ENDLOCAL & (
+ @IF "%~2" neq "" (
+ @SET "%rtrn%=%result%"
+ ) ELSE (
+ @ECHO stringToLower %string% result: %result%
+ )
+)
+@EXIT /B
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+:stringTrimTrailingUnderscores
+@SETLOCAL
+ @SET "rtrn=%~2"
+ @SET "string=%~1"
+ @SET "trimstring=%~1"
+ @REM trim up to 63 underscores from the end of a string using string substitution
+ @SET "trimstring=%trimstring%###"
+ @SET "trimstring=%trimstring:________________________________###=###%"
+ @SET "trimstring=%trimstring:________________###=###%"
+ @SET "trimstring=%trimstring:________###=###%"
+ @SET "trimstring=%trimstring:____###=###%"
+ @SET "trimstring=%trimstring:__###=###%"
+ @SET "trimstring=%trimstring:_###=###%"
+ @SET "trimstring=%trimstring:###=%"
+ @SET "result=!trimstring!"
+@ENDLOCAL & (
+ @IF "%~2" neq "" (
+ @SET "%rtrn%=%result%"
+ ) ELSE (
+ @ECHO stringTrimTrailingUnderscores %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 padding
+@REM padding
+@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 ./scriptname.cmd in sh or zsh or bash
+# -- e.g tclsh scriptname.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 '@
+#---------------------------------------------------------------------
+#divert to configured nextshell
+package require platform
+set plat_full [platform::generic]
+set plat [lindex [split $plat_full -] 0]
+#may be old tcl - not assuming readFile available
+set fd [open [info script] r]
+set scriptdata [read $fd]
+close $fd
+set scriptdata [string map [list \r\n \n] $scriptdata]
+set in_data 0
+set nextshellpath ""
+set nextshelltype ""
+puts stderr "PLAT: $plat"
+foreach ln [split $scriptdata \n] {
+ if {[string trim $ln] eq ""} {continue}
+ if {!$in_data} {
+ if {[string match ": <>*" $ln]} {
+ set in_data 1
+ }
+ } else {
+ if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
+ set lineparts [split $ln =]
+ set tail [lindex $lineparts 1]
+ set nextshellpath [string trimright $tail {_"}]
+ if {$nextshellpath ne "" && $nextshelltype ne ""} {
+ break
+ }
+ } elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
+ set lineparts [split $ln =]
+ set tail [lindex $lineparts 1]
+ set nextshelltype [string trimright $tail {_"}]
+ if {$nextshellpath ne "" && $nextshelltype ne ""} {
+ break
+ }
+ } elseif {[string match ": <>*" $ln]} {
+ break
+ }
+ }
+}
+if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
+ if {$nextshelltype in "pwsh powershell"} {
+ set scrname [file rootname [info script]].ps1
+ set arglist [list]
+ foreach a $::argv {
+ set a "'$a'"
+ lappend arglist $a
+ }
+ } else {
+ set scrname [info script]
+ set arglist $::argv
+ }
+ puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
+ #todo - handle /usr/bin/env
+ #todo - exitcode
+ if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
+ set nextshell_words [list $nextshellpath]
+ } else {
+ set nextshell_words $nextshellpath
+ }
+ set ns_firstword [lindex $nextshellpath 0]
+ if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
+ set ns_firstword [string range $ns_firstword 1 end-1]
+ }
+
+ if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
+ set exec_part $nextshellpath
+ } else {
+ set epath [auto_execok $ns_firstword]
+ if {$epath eq ""} {
+ error "unable to find executable path for first word '$ns_firstword' of nextshellpath '$nextshellpath'"
+ } else {
+ set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
+ }
+ }
+ catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
+
+ if {[dict exists $eopts -errorcode]} {
+ set ecode [dict get $eopts -errorcode]
+ if {[lindex $ecode 0] eq "CHILDSTATUS"} {
+ exit [lindex $ecode 2]
+ } else {
+ puts stderr "error calling next shell $nextshelltype :"
+ puts stderr $emsg
+ exit 1
+ }
+ } else {
+ exit 0
+ }
+}
+#---------------------------------------------------------------------
+
+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"
+# -- --- --- --- --- --- --- --- --- --- --- ---
+
+#
+puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
+#
+
+#
+#
+
+#
+#
+
+
+#
+#
+
+
+# -- --- --- --- --- --- --- --- --- --- --- ---
+# -- 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
+# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
+echo "var0: $0 @: $@"
+# echo "script: `echo $0 | sed 's/^-//'`"
+# use oldschool backticks and sed - lowest common denominator \
+# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
+# zsh diversion \
+# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
+# \
+ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
+# \
+echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
+# non-bash-like diversion \
+if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
+# sh/bash (or zsh?) \
+shift && set -- "${@:1:$((${#@}-1))}"
+# \
+#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+# \
+echo "args: $@"
+# ------------------------------------------------------
+# -- 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.
+# --
+# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+plat=$(uname -s) #platform/system
+if [[ "$plat" = "Linux"* ]]; then
+ os="linux"
+elif [[ "$plat" == "Darwin"* ]]; then
+ os="macosx"
+elif [[ "$plat" == "FreeBSD"* ]]; then
+ os="freebsd"
+elif [[ "$plat" == "DragonFly"* ]]; then
+ os="dragonflybsd"
+elif [[ "$plat" == "NetBSD"* ]]; then
+ os="netbsd"
+elif [[ "$plat" == "OpenBSD"* ]]; then
+ os="openbsd"
+elif [[ "$plat" = "MINGW32"* ]]; then
+ os="win32"
+elif [[ "$plat" = "MINGW64"* ]]; then
+ os="win32"
+elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+ os="win32"
+elif [[ "$plat" == "MSYS_NT"* ]]; then
+ #review..
+ echo MSYS
+ #win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
+ #bash reports $OSTYPE msys
+ os="win32"
+ #review - need ps/sed/awk to determine shell?
+ interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
+ #use 'command -v' (shell builtin preferred over external which)
+ shellpath=`command -v $interp`
+ shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
+ #"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
+ #This breaks calls to various unix utils such as sed etc (wsl related?)
+ export PATH="$shellfolder${PATH:+:${PATH}}"
+elif [[ "$OSTYPE" == "win32" ]]; then
+ os="win32"
+else
+ #os="$OSTYPE"
+ os="other"
+fi
+echo ostype: $OSTYPE
+## This is the sort of sed that will not work across implementations
+## shellconfiglines=$( sed -n "/: <>/{:a;n;/: <>/q;p;ba}" "$0" | grep $os)
+#awk tested on linux & freebsd
+shellconfiglines=$( awk '/^:.*<>.*$/,/^:.*<>.*$/' "$0" | grep $os)
+# echo $shellconfiglines;
+# readarray requires bash 4.0
+if [[ "$ps_shellname" == "bash" ]]; then
+ readarray -t arr_oslines <<<"$shellconfiglines"
+elif [[ "$ps_shellname" == "zsh" ]]; then
+ arr_oslines=("${(f)shellconfiglines}")
+else
+ #fallback - doesn't seem to work in zsh - untested in early bash
+ IFS=$'\n' arr_oslines=($shellconfiglines)
+fi
+nextshellpath=""
+nextshelltype=""
+for ln in "${arr_oslines[@]}"; do
+ # echo "---- $ln"
+ if [[ "$ln" == *"nextshellpath"* ]]; then
+ splitln="${ln#*=}" #remove everything through the first '='
+ pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
+ #remove trailing underscores (% means must match at end)
+ nextshellpath="${pathraw/%_*/}"
+ echo "nextshellpath: $nextshellpath"
+ elif [[ "$ln" == *"nextshelltype"* ]]; then
+ splitln="${ln#*=}"
+ typeraw="${splitln%%\"*}"
+ nextshelltype="${typeraw/%_*/}"
+ echo "nextshelltype: $nextshelltype"
+ fi
+done
+
+exitcode=0
+#-- sh/bash launches nextscript here instead of shebang line at top
+if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
+ echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ #e.g /usr/bin/env tclsh "$0" "$@"
+ ${nextshellpath} "$0" "$@"
+
+ exitcode=$?
+ #echo "sh/bash reporting exitcode: ${exitcode}"
+ exit $exitcode
+ #-- override exitcode example
+ #exit 66
+else
+ #already in bash - don't launch another process or we would loop
+ #echo "bash payload"
+ :
+fi
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
+#printf "start of bash or sh code"
+
+#
+
+
+git_upstream="https://gitea1.intx.com.au/jn/punkshell.git"
+#review - how can we test if another url such as ssh://git@pcm-gitea1.corp.intx.com.au:2222/jn/punkshell is actually the same repo, without cloning and comparing files/history?
+
+if ! command -v git &> /dev/null; then
+ echo "Git is not available. Please install git and ensure it is available on the path."
+ exit 1
+fi
+wdir="$(pwd)"; [ "$(pwd)" = "/" ] && wdir=""
+case "$0" in
+ /*) scriptpath="${0}";;
+ *) scriptpath="$wdir/${0#./}";;
+esac
+scriptdir="${scriptpath%/*}"
+echo "script: $0"
+echo "pwd: $(pwd)"
+echo "scriptdir: $scriptdir"
+echo "scriptpath: $scriptpath"
+scriptdir=$(realpath $scriptdir)
+scriptpath=$(realpath $scriptpath)
+
+basename=$(basename "$scriptpath")
+scriptroot="${basename%.*}" #e.g "getpunk"
+
+launchdir=$(pwd)
+if [[ "$launchdir" != "$scriptdir" ]]; then
+ echo "The current directory does not seem to be the folder in which the ${scriptroot} script is located."
+ read -p "Do you want to use the current directory '${launchdir}' as the location for punkshell? (Y|N)"$'\n'" Y - use launchdir ${launchdir}"$'\n'" N - use script folder '${scriptdir}'"$'\n'" (Any other value to abort): " answer
+ if [[ "${answer^^}" == "Y" ]]; then
+ punkfolder=$launchdir
+ elif [[ "${answer^^}" == "N" ]]; then
+ punkfolder=$scriptdir
+ else
+ exit 1
+ fi
+else
+ punkfolder=$scriptdir
+fi
+cd $punkfolder
+
+contentcount=$(ls -A | wc -l)
+effectively_empty=0
+if [ $contentcount == 0 ]; then
+ effectively_empty=1
+elif [[ ("$punkfolder" == "$scriptdir") && ("$contentcount" -lt 10) ]]; then
+ #treat as empty if we have only a few files matching script root name
+ count_scriptlike=$(ls ${scriptroot}.* | wc -l)
+ if [ "$count_scriptlike" -eq $contentcount ]; then
+ effectively_empty=1
+ fi
+fi
+
+if [ "$effectively_empty" -ne 1 ]; then
+ if ! [ -d "$punkfolder/.git" ]; then
+ echo "The folder '${punkfolder}' contains other items, and it does not appear to be a git project"
+ echo "Please place this script in an empty folder which is to be the punkshell base folder."
+ exit
+ else
+ repo_origin=$(git remote get-url origin)
+ if [ "$repo_origin" != "$git_upstream" ]; then
+ echo "The current repository origin '${repo_origin}' is not the expected upstream '${git_upstream}'"
+ read -p "Continue anyway? (Y|N)" answer
+ if [[ "${answer^^}" != "Y" ]]; then
+ exit 1
+ fi
+ fi
+ fi
+fi
+
+if ! [ -d "$punkfolder/.git" ]; then
+ #set defaultbranch to master to suppress loud stderr 'hint' about initial branch name.
+ git -c init.DefaultBranch=master init
+ git remote add origin $git_upstream
+fi
+git fetch origin
+if [[ "$punkfolder" == "$scriptdir" ]]; then
+ if [ -f $scriptroot.cmd ]; then
+ cp -f $scriptroot.cmd $scriptroot.cmd.lastrun
+ rm $scriptroot.cmd
+ fi
+fi
+git pull $git_upstream master
+git checkout $scriptroot.cmd
+git branch --set-upstream-to=origin/master master
+
+cd $launchdir #restore original CWD
+
+
+
+
+#
+
+#
+#
+
+# -- --- --- --- --- --- --- ---
+#
+#-- 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
+#
+# -- --- --- --- --- --- --- ---
+
+#
+#
+
+
+#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
+my $exit_code = 0;
+use Cwd qw(abs_path);
+my $scriptname = abs_path($0);
+#print "perl $scriptname\n";
+my $os = "$^O";
+if ($os eq "MSWin32") {
+ $os = "win32";
+} elsif ($os eq "darwin") {
+ $os = "macosx";
+}
+print "os $os\n";
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
+#use ExtUtils::Installed;
+#my $installed = ExtUtils::Installed->new();
+#my @modules = $installed->modules();
+#print "Modules:\n";
+#foreach my $m (@modules) {
+# print "$m\n";
+#}
+# -- --- ---
+
+
+
+my $i =1;
+foreach my $a(@ARGV) {
+ print "Arg # $i: $a\n";
+}
+
+#
+print STDERR "No perl code for this script. Try another program such as tcl or bash";
+#
+
+#
+#
+
+
+
+# -- --- --- --- --- --- --- ---
+#
+#$exit_code=system("tclsh", $scriptname, @ARGV);
+#print "perl reporting tcl exitcode: $exit_code";
+#
+# -- --- --- --- --- --- --- ---
+
+#
+#
+
+
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---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
+ }
+}
+# Example usage:
+# 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,
+# [Parameter(ValueFromRemainingArguments)] $opts
+# )
+#}
+#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) {
+# 'myargument' {
+# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
+# }
+# 'opts' {
+# write-warning "Unused parameters: $($PSBoundParameters.$_)"
+# }
+# Default {
+# write-warning "Unhandled parameter -> [$($_)]"
+# }
+# }
+# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
+# #...
+# }
+# }
+# end {
+# #Main function logic
+# Write-Host "myargument value is: $myargument"
+# #myotherfunction @PSBoundParameters
+# }
+#}
+#psmain @args
+#"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
+# -- --- --- ---
+$thisfileContent = Get-Content $scriptname -Raw
+$startTag = ": <>"
+$endTag = ": <>"
+$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
+$match = [regex]::Match($thisfileContent,$pattern)
+$asadmin = 0
+if ($match.Success) {
+ $admininfo = $match.Groups[1].Value
+ $asadmin = $admininfo.Contains("asadmin=1")
+ if ($asadmin) {
+ if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ # If not elevated, relaunch with elevated privileges
+ # -Wait e.g for starting a service or other operations which remainder of script may depend on
+ $arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass")
+ $arguments += @("-File", $($MyInvocation.MyCommand.Path))
+ $arguments += $args
+ if ($PSVersionTable.PSEdition -eq 'Core') {
+ Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs
+ } else {
+ Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs
+ }
+ Exit # Exit the current non-elevated process
+ }
+ }
+}
+#
+$startTag = ": <>"
+$endTag = ": <>"
+$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
+$match = [regex]::Match($thisfileContent,$pattern)
+if ($match.Success) {
+ $plat = [System.Environment]::OSVersion.Platform
+ if ($plat -eq "Unix") {
+ $runtime_ident = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier
+ switch ($runtime_ident.split("-")[0]) {
+ "freebsd" {
+ # untested
+ $os = "freebsd"
+ }
+ "linux" {
+ $os = "linux"
+ }
+ "osx" {
+ # osx-x64 or osx-arm64 ?
+ $os = "macosx"
+ }
+ default {
+ #openbsd, netbsd ?
+ $os = "other"
+ }
+ }
+ } else {
+ $os = "win32"
+ }
+
+ $matchedlines = $match.Groups[1].Value
+ $nextshell_type = ""
+ $nextshell_path = ""
+ ForEach ($line in $($matchedlines -split "\r?\n")) {
+ $m = [regex]::Match($line,".*nextshelltype\[${os}[_]+\]=([^_]*)[_]*")
+ if ($m.Success) {
+ $nextshell_type = $m.Groups[1].Value
+ }
+ $m = [regex]::Match($line,".*nextshellpath\[${os}[_]+\]=([^_]*)[_]*")
+ if ($m.Success) {
+ $nextshell_path = $m.Groups[1].Value
+ }
+ if ($nextshell_type -ne "" -and $nextshell_path -ne "") {
+ break
+ }
+ }
+ if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) {
+ #nextshell diversion exists for this platform
+ write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname"
+
+ # $arguments = @($($MyInvocation.MyCommand.Path))
+ # $arguments += $args
+ # NOTE - this gives incorrect argument quoting e.g wrong number of arguments received by launched process for arguments: a "b c"
+ # $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait)
+ # Exit $process.ExitCode
+
+ & $nextshell_path $scriptname $args
+ exit $LASTEXITCODE
+ }
+}
+
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
+
+#
+
+#powershell -Command "Invoke-WebRequest -Uri 'https://www.gitea1.intx.com.au/jn/punkshell/raw/branch/master/getpunk.cmd' -OutFile 'getpunk.cmd'; Start-Process 'getpunk.cmd' -NoNewWindow -Wait"
+
+#todo - support either fossil or git
+
+#check if git available
+#if not, check/install winget, winget git
+
+$git_upstream = "https://gitea1.intx.com.au/jn/punkshell.git"
+$launchdir = Get-Location #store original CWD
+$scriptfolder = Resolve-Path (Split-Path -Path $PSCommandPath -Parent)
+$punkfolder = ""
+$scriptroot = "$([System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath))"
+$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+
+if (-not (Get-Command "git" -ErrorAction SilentlyContinue)) {
+ Write-Host "The git command doesn't seem to be available. Will attempt to install using winget."
+ #Find-Module/Install-Module: older mechanism, available in powershell
+ #Find-PSResource/Install-PSResource: only available in newer pwsh etc?
+ $wgclient = Get-Module -ListAvailable -Name Microsoft.WinGet.Client
+ if (${wgclient}.Length -eq 0) {
+ Write-Host "Microsoft.WinGet.Client module not installed.. will try to install."
+ Install-PackageProvider -Name NuGet -Force
+ $psgallery_existing_policy = (Get-PSRepository -Name PSGallery).InstallationPolicy
+ if ($psgallery_existing_policy -eq "Untrusted") {
+ #Applies to all versions of PowerShell for the user, and is persistent for current user.
+ #This has risks in that a powershell session started after this call, and before we reset it, will treat PSGallery as trusted
+ Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
+ }
+ Install-Module -Scope CurrentUser -Name Microsoft.Winget.Client -Force -Repository PSGallery
+ Repair-WinGetPackageManager
+ import-module -name Microsoft.Winget.client
+
+ if ($psgallery_existing_policy -eq "Untrusted") {
+ Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted
+ }
+ } else {
+ Write-Host "Microsoft.WinGet.Client is available"
+ }
+ $gitversion = (Find-WinGetPackage Git.Git).Version
+ if ($gitversion) {
+ Write-Host "Installing git version: ${gitversion}"
+ Install-WinGetPackage -Id "Git.Git"
+ } else {
+ Write-Host "Failed to find git using winget"
+ exit
+ }
+ #refreshing the current session's path should make the new command available (required for powershell 5 at least)
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+ if (Get-Command "git" -ErrorAction SilentlyContinue) {
+ Write-Host "git is now available"
+ } else {
+ Write-Host "git is still not available"
+ Write-HOst "Please install Git or relaunch your terminal and check it is available on the path."
+ exit
+ }
+}
+
+if (($launchdir.Path) -ne ($scriptfolder.Path)) {
+ Write-Host "The current directory does not seem to be the folder in which the getpunk script is located."
+ $answer = Read-Host "Do you want to use the current directory '$($launchdir.Path) as the location for punkshell? Y|N`n Y to use launchdir '$($launchdir.Path)'`n 'N' to use script folder '$($scriptfolder.Path)`n Any other value to abort: "
+ if ($answer -match "y") {
+ $punkfolder = $launchdir
+ } elseif ($answer -match "n") {
+ $punkfolder = $scriptfolder
+ } else {
+ exit 1
+ }
+
+} else {
+ $punkfolder = $scriptfolder
+}
+$punkfoldercontents = Get-ChildItem -Path $punkfolder -Force #include possibly hidden items such as .git folder
+$contentcount = ( $punkfoldercontents | Measure-Object).Count
+$effectively_empty = 0
+if ($contentcount -eq 0) {
+ $effectively_empty = 1
+} elseif ($punkfolder -eq $scriptfolder -and $contentcount -lt 10) {
+ #treat as empty if we have only a few files matching script root name
+ $scriptlike = get-childitem -Path $punkfolder | Where-Object {$_.name -like "${scriptroot}.*"}
+ if ($scriptlike.Count -eq $contentcount) {
+ $effectively_empty = 1
+ }
+}
+if (-not($effectively_empty)) {
+ if (-not(Test-Path -Path (Join-Path -Path $punkfolder -ChildPath ".git") -PathType Container)) {
+ Write-Host "The folder $punkfolder contains other items, and it does not appear to be a git project root."
+ Write-Host "Please place this script in an empty folder which is to be the punkshell base folder."
+ exit
+ } else {
+ $repo_origin = git remote get-url origin
+ if ($repo_origin -ne $git_upstream) {
+ Write-Host "The current repository origin '$repo_origin' is not the expected upstream '${git_upstream}'"
+ $answer = Read-Host "Continue anyway? (Y|N)"
+ if (-not($answer -match "y")) {
+ exit 1
+ }
+ }
+ }
+} else {
+ #punkfolder is empty, or has just the current script
+}
+Set-Location -Path $punkfolder
+
+
+if (-not(Test-Path -Path (Join-Path -Path $punkfolder -ChildPath ".git") -PathType Container)) {
+ git init
+ git remote add origin $git_upstream
+}
+git fetch origin
+if (($launchdir.Path) -eq ($scriptfolder.Path)) {
+ if (Test-Path -Path "${scriptroot}.cmd") {
+ #rename-item won't allow overwriting existing target file
+ Move-Item -Path "${scriptroot}.cmd" -Destination "${scriptroot}.cmd.lastrun" -Force
+ }
+}
+git pull $git_upstream master
+git checkout "${scriptroot}.cmd"
+git branch --set-upstream-to=origin/master master
+
+Set-Location $launchdir #restore original CWD
+#see also: https://github.com/jdhitsolutions/WTToolbox
+
+# Define the necessary Win32 API functions and constants
+Add-Type -TypeDefinition @"
+using System;
+using System.Runtime.InteropServices;
+
+public class WinAPI
+{
+ // Console Input/Output Handles
+ public const int STD_OUTPUT_HANDLE = -11;
+ public const uint ENABLE_QUICK_EDIT_MODE = 0x0040;
+ public const uint ENABLE_EXTENDED_FLAGS = 0x0080;
+ public const uint ENABLE_MOUSE_INPUT = 0x0010;
+ public const uint ENABLE_WINDOW_INPUT = 0x0008;
+ public const uint ENABLE_INSERT_MODE = 0x0020;
+ public const uint ENABLE_LINE_INPUT = 0x0002;
+ public const uint ENABLE_ECHO_INPUT = 0x0004;
+ public const uint ENABLE_PROCESSED_INPUT = 0x0001;
+
+ // Console Modes
+ public const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
+ public const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr GetStdHandle(int nStdHandle);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
+}
+"@
+
+# Get the handle to the console output buffer
+$stdoutHandle = [WinAPI]::GetStdHandle([WinAPI]::STD_OUTPUT_HANDLE)
+
+# Get the current console mode
+[uint32]$currentMode = 0
+if (![WinAPI]::GetConsoleMode($stdoutHandle, [ref]$currentMode)) {
+ Write-Error "Failed to get console mode. Error code: $($LAST_ERROR)"
+ return
+}
+
+# Enable virtual terminal processing
+$newMode = $currentMode -bor [WinAPI]::ENABLE_VIRTUAL_TERMINAL_PROCESSING
+
+# Set the new console mode
+if (-not [WinAPI]::SetConsoleMode($stdoutHandle, $newMode)) {
+ Write-Error "Failed to set console mode. Error code: $($LAST_ERROR)"
+ return
+}
+
+Write-Host "Virtual terminal processing enabled successfully."
+
+write-host "`e[92m getpunk done `e[m"
+
+#
+
+#
+#
+
+
+# -- --- --- --- --- --- --- ---
+#
+#tclsh $scriptname $args
+#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
+#
+# -- --- --- --- --- --- --- ---
+
+
+#
+#
+
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---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
+#
+
+#
+# -- unreachable by tcl directly if ctrl-z character is in the 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)
+#>
+
+
+
diff --git a/getpunk.ps1 b/getpunk.ps1
new file mode 100644
index 00000000..e1bc3be5
--- /dev/null
+++ b/getpunk.ps1
@@ -0,0 +1,1596 @@
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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, (some 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 -------------------------------------------------------------------------------------------------------------------------------
+@rem return from endlocal macro - courtesy of jeb
+@rem This allows return of values containing special characters from subroutines
+@rem https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/8257951#8257951
+@rem -------------------------------------------------------------------------------------------------------------------------------
+@setlocal DisableDelayedExpansion
+@echo off
+%= 2 blank lines after next are required =%
+set LF=^
+
+
+set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
+%= I use EDE for EnableDelayeExpansion and DDE for DisableDelayedExpansion =%
+set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
+ setlocal EnableDelayedExpansion%\n%
+ %= Take all variable names into the varName array =%%\n%
+ set varName_count=0%\n%
+ for %%C in (!args!) do set "varName[!varName_count!]=%%~C" ^& set /a varName_count+=1%\n%
+ %= Build one variable with a list of set statements for each variable delimited by newlines =%%\n%
+ %= The lists looks like --> set result1=myContent\n"set result1=myContent1"\nset result2=content2\nset result2=content2\n =%%\n%
+ %= Each result exists two times, the first for the case returning to DDE, the second for EDE =%%\n%
+ %= The correct line will be detected by the (missing) enclosing quotes =%%\n%
+ set "retContent=1!LF!"%\n%
+ for /L %%n in (0 1 !varName_count!) do (%\n%
+ for /F "delims=" %%C in ("!varName[%%n]!") DO (%\n%
+ set "content=!%%C!"%\n%
+ set "retContent=!retContent!"set !varName[%%n]!=!content!"!LF!"%\n%
+ if defined content (%\n%
+ %= This complex block is only for replacing '!' with '^!' =%%\n%
+ %= First replacing '"'->'""q' '^'->'^^' =%%\n%
+ set ^"content_EDE=!content:"=""q!"%\n%
+ set "content_EDE=!content_EDE:^=^^!"%\n%
+ %= Now it's poosible to use CALL SET and replace '!'->'""e!' =%%\n%
+ call set "content_EDE=%%content_EDE:^!=""e^!%%"%\n%
+ %= Now it's possible to replace '""e' to '^', this is effectivly '!' -> '^!' =%%\n%
+ set "content_EDE=!content_EDE:""e=^!"%\n%
+ %= Now restore the quotes =%%\n%
+ set ^"content_EDE=!content_EDE:""q="!"%\n%
+ ) ELSE set "content_EDE="%\n%
+ set "retContent=!retContent!set "!varName[%%n]!=!content_EDE!"!LF!"%\n%
+ )%\n%
+ )%\n%
+ %= Now return all variables from retContent over the barrier =%%\n%
+ for /F "delims=" %%V in ("!retContent!") DO (%\n%
+ %= Only the first line can contain a single 1 =%%\n%
+ if "%%V"=="1" (%\n%
+ %= We need to call endlocal twice, as there is one more setlocal in the macro itself =%%\n%
+ endlocal%\n%
+ endlocal%\n%
+ ) ELSE (%\n%
+ %= This is true in EDE =%%\n%
+ if "!"=="" (%\n%
+ if %%V==%%~V (%\n%
+ %%V !%\n%
+ )%\n%
+ ) ELSE IF not %%V==%%~V (%\n%
+ %%~V%\n%
+ )%\n%
+ )%\n%
+ )%\n%
+ ) else set args="
+
+@rem -------------------------------------------------------------------------------------------------------------------------------
+@SETLOCAL EnableExtensions EnableDelayedExpansion
+@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
+@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
+@REM e.g from within a running punkshell: dev scriptwrap.multishell -outputfolder
+@REM Call with sh, bash, perl, or tclsh. (powershell untested on unix)
+@REM Due to lack of shebang (#! line) Unix-like systems will hopefully default to a flavour of sh that can divert to bash 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.
+@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
+@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
+@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
+@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
+@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
+@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
+@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
+@REM Supporting more explicit oses than those listed may also require script padding adjustment
+: <>
+@SET "nextshellpath[win32___________]=powershell -nop -nol -ExecutionPolicy ByPass -File______________"
+@SET "nextshelltype[win32___________]=powershell______"
+@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[dragonflybsd____]=bash____________"
+@SET "nextshellpath[freebsd_________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[freebsd_________]=bash____________"
+@SET "nextshellpath[netbsd__________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[netbsd__________]=bash____________"
+@SET "nextshellpath[linux___________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[linux___________]=bash____________"
+@SET "nextshellpath[macosx__________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[macosx__________]=bash____________"
+@SET "nextshellpath[other___________]=/usr/bin/env bash_______________________________________________"
+@SET "nextshelltype[other___________]=bash____________"
+: <>
+@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).
+: <>
+@SET "asadmin=0"
+: <>
+@SET "selected_shelltype=%nextshelltype[win32___________]%"
+@REM @ECHO selected_shelltype %selected_shelltype%
+@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
+@REM @ECHO selected_shelltype_trimmed %selected_shelltype_trimmed%
+@SET "selected_shellpath=%nextshellpath[win32___________]%"
+@CALL :stringTrimTrailingUnderscores %selected_shellpath% selected_shellpath_trimmed
+@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%"
+@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" %= e.g c:\punkshell\bin\ %=
+@SET "fname=%~nx0"
+@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
+@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 padding
+@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 window 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 "!selected_shelltype_trimmed!"=="none" (
+ SET selected_shelltype_trimmed=pwsh
+)
+
+
+
+
+@set argCount=30
+@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
+@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
+@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
+
+@set tmpfile_base=%TEMP%\punkbatch_params
+@call :getUniqueFile %tmpfile_base% ".txt" paramfile
+@echo %paramfile%
+
+%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
+@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
+@rem outer loop required to redirect all rem lines at once to file
+@for %%x in (1) do @(
+ @for /L %%f in (1,1,%argCount%) do @(
+ @set "argnum=%%~nf"
+ @set "a1=%%1"
+ @rem @set "argname=%%!argnum!"
+ @rem @echo argname: !argname!
+ @call :rem_output !argnum! !a1!
+ @shift
+ )
+) > %paramfile%
+@echo off
+
+@set "newcommandline= "
+
+@(set target=cmd_pwsh)
+@if "%target%"=="cmd_pwsh" (
+ @for /F "delims=" %%L in (%paramfile%) do @(
+ SETLOCAL DisableDelayedExpansion
+ set "param=%%L"
+ @REM @echo ######### %%L
+ @rem call :buildcmdline newcommandline param "{" "}"
+ call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell %=
+ @rem @echo .
+ )
+) ELSE (
+ @for /F "delims=" %%L in (%paramfile%) do @(
+ SETLOCAL DisableDelayedExpansion
+ set "param=%%L"
+ call :buildcmdline newcommandline param
+ )
+)
+@REM padding
+SETLOCAL EnableDelayedExpansion
+
+@echo off
+@IF EXIST %paramfile% (
+ @DEL /F /Q %paramfile%
+)
+@IF EXIST %paramfile% (
+ echo failed to delete %paramfile%
+ cat %paramfile%
+)
+
+
+
+@REM @SET "squoted_args="
+@REM @for %%a in (%*) do @(
+@REM set "v=%%a"
+@REM set "v=!v:'=''!"
+@REM SET "squoted_args=!squoted_args!'!v!' "
+@REM )
+@REM @SET "squoted_args=%squoted_args:~0,-1%"
+@REM @ECHO %squoted_args%
+
+
+@IF "!selected_shelltype_trimmed!"=="pwsh" (
+ REM pwsh 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 2>NUL; 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 (
+ @rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
+ @rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
+ cmd /c pwsh -nop -nol -ExecutionPolicy bypass -c "%scriptrootname%.ps1" !newcommandline!
+ SET task_exitcode=!errorlevel!
+ ) ELSE (
+ REM TODO prompt user with option to call script to install pwsh using winget
+ @rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
+ cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
+ SET task_exitcode=!errorlevel!
+ )
+) ELSE (
+ IF "!selected_shelltype_trimmed!"=="powershell" (
+ @rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
+ cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
+ SET task_exitcode=!errorlevel!
+ ) ELSE (
+ IF "!selected_shelltype_trimmed!"=="wslbash" (
+ CALL :getWslPath %winpath% wslpath
+ REM ECHO wslfullpath "!wslpath!%fname%"
+ %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
+ SET task_exitcode=!errorlevel!
+ ) ELSE (
+ REM perl or tcl or sh or bash
+ IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
+ 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
+ REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
+ @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
+ %selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
+ ) ELSE (
+ ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
+ SET task_exitcode=66
+ @REM boundary padding
+ GOTO :exit_multishell
+ )
+ )
+ )
+)
+@REM batch file library functions
+
+@GOTO :endlib
+
+@REM padding
+@REM padding
+@REM padding
+
+%= ---------------------------------------------------------------------- =%
+@rem courtesy of dbenham
+:: Example usage
+@rem call :getUniqueFile "d:\test\myFile" ".txt" myFile
+@rem echo myFile="%myFile%"
+
+:getUniqueFile baseName extension rtnVar
+setlocal
+:getUniqueFileLoop
+for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
+if exist "%rtn%" (
+ goto :getUniqueFileLoop
+) else (
+ 2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
+)
+endlocal & set "%~3=%rtn%"
+exit /b
+%= ---------------------------------------------------------------------- =%
+
+:buildcmdline cmdlinevar paramvar wrapA wrapB
+ %= quoting for cmd.exe /c pwsh -nop !args! =%
+ @SETLOCAL EnableDelayedExpansion
+
+ @REM @echo =====================
+ set "pval=!%~2:*#=!"
+ set "pval=!pval:~0,-2!"
+ @REM set "pval=!pval:~0,-1!"
+ set "wrapa=%~3"
+ set "wrapb=%~4"
+
+ @call :strlen pval slen
+ @rem @echo strlen: !slen!
+ if "!slen!"=="0" (
+ goto :eof
+ )
+ @set /A n = !slen! - 1
+ @(set str=)
+ @set "dq=""
+ @set "bang=^!"
+ @(set carat=^^)
+ @for /l %%i in (0,1,!n!) do @(
+ (set c=!pval:~%%i,1!)
+ if "!c!"=="|" (
+ set "ch=^^!pval:~%%i,1!"
+ ) ELSE IF "!c!"=="(" (
+ set "ch=^("
+ ) ELSE if "!c!"==")" (
+ set "ch=^)"
+ ) ELSE if "!c!"=="&" (
+ set "ch=^^&"
+ ) ELSE if "!c!"=="!dq!" (
+ set "ch=^""
+ ) ELSE if "!c!"=="!bang!" (
+ @rem - double caret - first for initial parsing, second to ensure remains escaped during delayed expansion phase
+ @rem - REVIEW
+ set "ch=^^!bang!"
+ ) ELSE if "!c!"=="^carat" (
+ set "ch=^^^^"
+ ) ELSE if "!c!"=="'" (
+ set "ch=''"
+ ) ELSE (
+ set "ch=!c!"
+ )
+ @rem @echo - !ch!
+ set "str=!str!!ch!"
+ )
+ echo +++++ %~1 = !%1! !str!
+
+ set "%~1=!%1! !wrapa!!str!!wrapb!"
+
+ @rem old method of return - failes to preserve exclamation marks
+ @rem for /f "delims=" %%A in (""!str!"") do endlocal & set "%~1=!%1! '%%~A'"
+ @rem macro method of endlocal return - preserving !val!
+ @echo off
+ %endlocal% %~1
+
+ @exit /b
+
+:rem_output
+ @rem ---------------------------------------------
+ @rem rem_output is called for each n in the number of args we process - as we don't have a non-destructive way to count args whilst accepting special chars
+ @rem we therefore need a way to stop processing on the last received arg so we don't write argCount entries to param.txt if less are received
+ @rem see 'disappearing quotes' technique
+ @rem https://stackoverflow.com/questions/4643376/how-to-split-double-quoted-line-into-multiple-lines-in-windows-batch-file/4645113#4645113
+ @rem and
+ @rem https://groups.google.com/g/alt.msdos.batch.nt/c/J71F17Bve9Y (sponge belly)
+ @echo off
+ setlocal enableextensions disabledelayedexpansion
+ set "param1=%~2"
+ rem do must not be indented
+ for %%^" in ("") ^
+do if not defined param1 set %%~"param1=%2%%~"
+ if not defined param1 goto :eof
+ endlocal
+ @rem ---------------------------------------------
+
+ @PROMPT @
+ @echo on
+ rem %1 #%2#
+@exit /b
+
+@REM courtesy of: https://stackoverflow.com/users/463115/jeb
+:strlen stringVar returnVar
+@(
+ setlocal EnableDelayedExpansion
+ @SET "rtrn=%~2"
+ (set^ tmp=!%~1!)
+ @rem @echo jjjjj !tmp!
+ @if defined tmp (
+ set "len=1"
+ @for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do @(
+ @if "!tmp:~%%P,1!" NEQ "" (
+ set /a "len+=%%P"
+ set "tmp=!tmp:~%%P!"
+ )
+ )
+ ) ELSE (
+ set len=0
+ )
+)
+@(
+ endlocal
+ @IF "%~2" neq "" (
+ @SET "%rtrn%=%len%"
+ ) ELSE (
+ @ECHO :strlen result: %len%
+ )
+ exit /b
+)
+
+
+:getWslPath
+@SETLOCAL
+ @SET "_path=%~p1"
+ @SET "name=%~nx1"
+ @SET "drive=%~d1"
+ @SET "rtrn=%~2"
+ @REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters
+ @CALL :stringToLower %drive ldrive
+ @SET "result=/mnt/%ldrive:~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
+
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+: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'
+@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
+
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+
+: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
+@REM boundary padding
+@REM boundary padding
+:stringToUpper strvar returnvar
+@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
+:stringToLower
+@SETLOCAL
+ @SET "rtrn=%~2"
+ @SET "string=%~1"
+ @SET "retstring=%~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 "retstring=!retstring:%%A=%%A!"
+ )
+ @SET "result=!retstring!"
+@ENDLOCAL & (
+ @IF "%~2" neq "" (
+ @SET "%rtrn%=%result%"
+ ) ELSE (
+ @ECHO stringToLower %string% result: %result%
+ )
+)
+@EXIT /B
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+@REM boundary padding
+:stringTrimTrailingUnderscores
+@SETLOCAL
+ @SET "rtrn=%~2"
+ @SET "string=%~1"
+ @SET "trimstring=%~1"
+ @REM trim up to 63 underscores from the end of a string using string substitution
+ @SET "trimstring=%trimstring%###"
+ @SET "trimstring=%trimstring:________________________________###=###%"
+ @SET "trimstring=%trimstring:________________###=###%"
+ @SET "trimstring=%trimstring:________###=###%"
+ @SET "trimstring=%trimstring:____###=###%"
+ @SET "trimstring=%trimstring:__###=###%"
+ @SET "trimstring=%trimstring:_###=###%"
+ @SET "trimstring=%trimstring:###=%"
+ @SET "result=!trimstring!"
+@ENDLOCAL & (
+ @IF "%~2" neq "" (
+ @SET "%rtrn%=%result%"
+ ) ELSE (
+ @ECHO stringTrimTrailingUnderscores %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 padding
+@REM padding
+@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 ./scriptname.cmd in sh or zsh or bash
+# -- e.g tclsh scriptname.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 '@
+#---------------------------------------------------------------------
+#divert to configured nextshell
+package require platform
+set plat_full [platform::generic]
+set plat [lindex [split $plat_full -] 0]
+#may be old tcl - not assuming readFile available
+set fd [open [info script] r]
+set scriptdata [read $fd]
+close $fd
+set scriptdata [string map [list \r\n \n] $scriptdata]
+set in_data 0
+set nextshellpath ""
+set nextshelltype ""
+puts stderr "PLAT: $plat"
+foreach ln [split $scriptdata \n] {
+ if {[string trim $ln] eq ""} {continue}
+ if {!$in_data} {
+ if {[string match ": <>*" $ln]} {
+ set in_data 1
+ }
+ } else {
+ if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
+ set lineparts [split $ln =]
+ set tail [lindex $lineparts 1]
+ set nextshellpath [string trimright $tail {_"}]
+ if {$nextshellpath ne "" && $nextshelltype ne ""} {
+ break
+ }
+ } elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
+ set lineparts [split $ln =]
+ set tail [lindex $lineparts 1]
+ set nextshelltype [string trimright $tail {_"}]
+ if {$nextshellpath ne "" && $nextshelltype ne ""} {
+ break
+ }
+ } elseif {[string match ": <>*" $ln]} {
+ break
+ }
+ }
+}
+if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
+ if {$nextshelltype in "pwsh powershell"} {
+ set scrname [file rootname [info script]].ps1
+ set arglist [list]
+ foreach a $::argv {
+ set a "'$a'"
+ lappend arglist $a
+ }
+ } else {
+ set scrname [info script]
+ set arglist $::argv
+ }
+ puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
+ #todo - handle /usr/bin/env
+ #todo - exitcode
+ if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
+ set nextshell_words [list $nextshellpath]
+ } else {
+ set nextshell_words $nextshellpath
+ }
+ set ns_firstword [lindex $nextshellpath 0]
+ if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
+ set ns_firstword [string range $ns_firstword 1 end-1]
+ }
+
+ if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
+ set exec_part $nextshellpath
+ } else {
+ set epath [auto_execok $ns_firstword]
+ if {$epath eq ""} {
+ error "unable to find executable path for first word '$ns_firstword' of nextshellpath '$nextshellpath'"
+ } else {
+ set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
+ }
+ }
+ catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
+
+ if {[dict exists $eopts -errorcode]} {
+ set ecode [dict get $eopts -errorcode]
+ if {[lindex $ecode 0] eq "CHILDSTATUS"} {
+ exit [lindex $ecode 2]
+ } else {
+ puts stderr "error calling next shell $nextshelltype :"
+ puts stderr $emsg
+ exit 1
+ }
+ } else {
+ exit 0
+ }
+}
+#---------------------------------------------------------------------
+
+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"
+# -- --- --- --- --- --- --- --- --- --- --- ---
+
+#
+puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
+#
+
+#
+#
+
+#
+#
+
+
+#
+#
+
+
+# -- --- --- --- --- --- --- --- --- --- --- ---
+# -- 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
+# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
+echo "var0: $0 @: $@"
+# echo "script: `echo $0 | sed 's/^-//'`"
+# use oldschool backticks and sed - lowest common denominator \
+# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
+# zsh diversion \
+# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
+# \
+ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
+# \
+echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
+# non-bash-like diversion \
+if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
+# sh/bash (or zsh?) \
+shift && set -- "${@:1:$((${#@}-1))}"
+# \
+#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+# \
+echo "args: $@"
+# ------------------------------------------------------
+# -- 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.
+# --
+# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
+
+plat=$(uname -s) #platform/system
+if [[ "$plat" = "Linux"* ]]; then
+ os="linux"
+elif [[ "$plat" == "Darwin"* ]]; then
+ os="macosx"
+elif [[ "$plat" == "FreeBSD"* ]]; then
+ os="freebsd"
+elif [[ "$plat" == "DragonFly"* ]]; then
+ os="dragonflybsd"
+elif [[ "$plat" == "NetBSD"* ]]; then
+ os="netbsd"
+elif [[ "$plat" == "OpenBSD"* ]]; then
+ os="openbsd"
+elif [[ "$plat" = "MINGW32"* ]]; then
+ os="win32"
+elif [[ "$plat" = "MINGW64"* ]]; then
+ os="win32"
+elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+ os="win32"
+elif [[ "$plat" == "MSYS_NT"* ]]; then
+ #review..
+ echo MSYS
+ #win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
+ #bash reports $OSTYPE msys
+ os="win32"
+ #review - need ps/sed/awk to determine shell?
+ interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
+ #use 'command -v' (shell builtin preferred over external which)
+ shellpath=`command -v $interp`
+ shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
+ #"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
+ #This breaks calls to various unix utils such as sed etc (wsl related?)
+ export PATH="$shellfolder${PATH:+:${PATH}}"
+elif [[ "$OSTYPE" == "win32" ]]; then
+ os="win32"
+else
+ #os="$OSTYPE"
+ os="other"
+fi
+echo ostype: $OSTYPE
+## This is the sort of sed that will not work across implementations
+## shellconfiglines=$( sed -n "/: <>/{:a;n;/: <>/q;p;ba}" "$0" | grep $os)
+#awk tested on linux & freebsd
+shellconfiglines=$( awk '/^:.*<>.*$/,/^:.*<>.*$/' "$0" | grep $os)
+# echo $shellconfiglines;
+# readarray requires bash 4.0
+if [[ "$ps_shellname" == "bash" ]]; then
+ readarray -t arr_oslines <<<"$shellconfiglines"
+elif [[ "$ps_shellname" == "zsh" ]]; then
+ arr_oslines=("${(f)shellconfiglines}")
+else
+ #fallback - doesn't seem to work in zsh - untested in early bash
+ IFS=$'\n' arr_oslines=($shellconfiglines)
+fi
+nextshellpath=""
+nextshelltype=""
+for ln in "${arr_oslines[@]}"; do
+ # echo "---- $ln"
+ if [[ "$ln" == *"nextshellpath"* ]]; then
+ splitln="${ln#*=}" #remove everything through the first '='
+ pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
+ #remove trailing underscores (% means must match at end)
+ nextshellpath="${pathraw/%_*/}"
+ echo "nextshellpath: $nextshellpath"
+ elif [[ "$ln" == *"nextshelltype"* ]]; then
+ splitln="${ln#*=}"
+ typeraw="${splitln%%\"*}"
+ nextshelltype="${typeraw/%_*/}"
+ echo "nextshelltype: $nextshelltype"
+ fi
+done
+
+exitcode=0
+#-- sh/bash launches nextscript here instead of shebang line at top
+if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
+ echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ #e.g /usr/bin/env tclsh "$0" "$@"
+ ${nextshellpath} "$0" "$@"
+
+ exitcode=$?
+ #echo "sh/bash reporting exitcode: ${exitcode}"
+ exit $exitcode
+ #-- override exitcode example
+ #exit 66
+else
+ #already in bash - don't launch another process or we would loop
+ #echo "bash payload"
+ :
+fi
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
+#printf "start of bash or sh code"
+
+#
+
+
+git_upstream="https://gitea1.intx.com.au/jn/punkshell.git"
+#review - how can we test if another url such as ssh://git@pcm-gitea1.corp.intx.com.au:2222/jn/punkshell is actually the same repo, without cloning and comparing files/history?
+
+if ! command -v git &> /dev/null; then
+ echo "Git is not available. Please install git and ensure it is available on the path."
+ exit 1
+fi
+wdir="$(pwd)"; [ "$(pwd)" = "/" ] && wdir=""
+case "$0" in
+ /*) scriptpath="${0}";;
+ *) scriptpath="$wdir/${0#./}";;
+esac
+scriptdir="${scriptpath%/*}"
+echo "script: $0"
+echo "pwd: $(pwd)"
+echo "scriptdir: $scriptdir"
+echo "scriptpath: $scriptpath"
+scriptdir=$(realpath $scriptdir)
+scriptpath=$(realpath $scriptpath)
+
+basename=$(basename "$scriptpath")
+scriptroot="${basename%.*}" #e.g "getpunk"
+
+launchdir=$(pwd)
+if [[ "$launchdir" != "$scriptdir" ]]; then
+ echo "The current directory does not seem to be the folder in which the ${scriptroot} script is located."
+ read -p "Do you want to use the current directory '${launchdir}' as the location for punkshell? (Y|N)"$'\n'" Y - use launchdir ${launchdir}"$'\n'" N - use script folder '${scriptdir}'"$'\n'" (Any other value to abort): " answer
+ if [[ "${answer^^}" == "Y" ]]; then
+ punkfolder=$launchdir
+ elif [[ "${answer^^}" == "N" ]]; then
+ punkfolder=$scriptdir
+ else
+ exit 1
+ fi
+else
+ punkfolder=$scriptdir
+fi
+cd $punkfolder
+
+contentcount=$(ls -A | wc -l)
+effectively_empty=0
+if [ $contentcount == 0 ]; then
+ effectively_empty=1
+elif [[ ("$punkfolder" == "$scriptdir") && ("$contentcount" -lt 10) ]]; then
+ #treat as empty if we have only a few files matching script root name
+ count_scriptlike=$(ls ${scriptroot}.* | wc -l)
+ if [ "$count_scriptlike" -eq $contentcount ]; then
+ effectively_empty=1
+ fi
+fi
+
+if [ "$effectively_empty" -ne 1 ]; then
+ if ! [ -d "$punkfolder/.git" ]; then
+ echo "The folder '${punkfolder}' contains other items, and it does not appear to be a git project"
+ echo "Please place this script in an empty folder which is to be the punkshell base folder."
+ exit
+ else
+ repo_origin=$(git remote get-url origin)
+ if [ "$repo_origin" != "$git_upstream" ]; then
+ echo "The current repository origin '${repo_origin}' is not the expected upstream '${git_upstream}'"
+ read -p "Continue anyway? (Y|N)" answer
+ if [[ "${answer^^}" != "Y" ]]; then
+ exit 1
+ fi
+ fi
+ fi
+fi
+
+if ! [ -d "$punkfolder/.git" ]; then
+ #set defaultbranch to master to suppress loud stderr 'hint' about initial branch name.
+ git -c init.DefaultBranch=master init
+ git remote add origin $git_upstream
+fi
+git fetch origin
+if [[ "$punkfolder" == "$scriptdir" ]]; then
+ if [ -f $scriptroot.cmd ]; then
+ cp -f $scriptroot.cmd $scriptroot.cmd.lastrun
+ rm $scriptroot.cmd
+ fi
+fi
+git pull $git_upstream master
+git checkout $scriptroot.cmd
+git branch --set-upstream-to=origin/master master
+
+cd $launchdir #restore original CWD
+
+
+
+
+#
+
+#
+#
+
+# -- --- --- --- --- --- --- ---
+#
+#-- 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
+#
+# -- --- --- --- --- --- --- ---
+
+#
+#
+
+
+#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
+my $exit_code = 0;
+use Cwd qw(abs_path);
+my $scriptname = abs_path($0);
+#print "perl $scriptname\n";
+my $os = "$^O";
+if ($os eq "MSWin32") {
+ $os = "win32";
+} elsif ($os eq "darwin") {
+ $os = "macosx";
+}
+print "os $os\n";
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
+#use ExtUtils::Installed;
+#my $installed = ExtUtils::Installed->new();
+#my @modules = $installed->modules();
+#print "Modules:\n";
+#foreach my $m (@modules) {
+# print "$m\n";
+#}
+# -- --- ---
+
+
+
+my $i =1;
+foreach my $a(@ARGV) {
+ print "Arg # $i: $a\n";
+}
+
+#
+print STDERR "No perl code for this script. Try another program such as tcl or bash";
+#
+
+#
+#
+
+
+
+# -- --- --- --- --- --- --- ---
+#
+#$exit_code=system("tclsh", $scriptname, @ARGV);
+#print "perl reporting tcl exitcode: $exit_code";
+#
+# -- --- --- --- --- --- --- ---
+
+#
+#
+
+
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---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
+ }
+}
+# Example usage:
+# 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,
+# [Parameter(ValueFromRemainingArguments)] $opts
+# )
+#}
+#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) {
+# 'myargument' {
+# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
+# }
+# 'opts' {
+# write-warning "Unused parameters: $($PSBoundParameters.$_)"
+# }
+# Default {
+# write-warning "Unhandled parameter -> [$($_)]"
+# }
+# }
+# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
+# #...
+# }
+# }
+# end {
+# #Main function logic
+# Write-Host "myargument value is: $myargument"
+# #myotherfunction @PSBoundParameters
+# }
+#}
+#psmain @args
+#"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
+# -- --- --- ---
+$thisfileContent = Get-Content $scriptname -Raw
+$startTag = ": <>"
+$endTag = ": <>"
+$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
+$match = [regex]::Match($thisfileContent,$pattern)
+$asadmin = 0
+if ($match.Success) {
+ $admininfo = $match.Groups[1].Value
+ $asadmin = $admininfo.Contains("asadmin=1")
+ if ($asadmin) {
+ if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ # If not elevated, relaunch with elevated privileges
+ # -Wait e.g for starting a service or other operations which remainder of script may depend on
+ $arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass")
+ $arguments += @("-File", $($MyInvocation.MyCommand.Path))
+ $arguments += $args
+ if ($PSVersionTable.PSEdition -eq 'Core') {
+ Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs
+ } else {
+ Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs
+ }
+ Exit # Exit the current non-elevated process
+ }
+ }
+}
+#
+$startTag = ": <>"
+$endTag = ": <>"
+$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
+$match = [regex]::Match($thisfileContent,$pattern)
+if ($match.Success) {
+ $plat = [System.Environment]::OSVersion.Platform
+ if ($plat -eq "Unix") {
+ $runtime_ident = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier
+ switch ($runtime_ident.split("-")[0]) {
+ "freebsd" {
+ # untested
+ $os = "freebsd"
+ }
+ "linux" {
+ $os = "linux"
+ }
+ "osx" {
+ # osx-x64 or osx-arm64 ?
+ $os = "macosx"
+ }
+ default {
+ #openbsd, netbsd ?
+ $os = "other"
+ }
+ }
+ } else {
+ $os = "win32"
+ }
+
+ $matchedlines = $match.Groups[1].Value
+ $nextshell_type = ""
+ $nextshell_path = ""
+ ForEach ($line in $($matchedlines -split "\r?\n")) {
+ $m = [regex]::Match($line,".*nextshelltype\[${os}[_]+\]=([^_]*)[_]*")
+ if ($m.Success) {
+ $nextshell_type = $m.Groups[1].Value
+ }
+ $m = [regex]::Match($line,".*nextshellpath\[${os}[_]+\]=([^_]*)[_]*")
+ if ($m.Success) {
+ $nextshell_path = $m.Groups[1].Value
+ }
+ if ($nextshell_type -ne "" -and $nextshell_path -ne "") {
+ break
+ }
+ }
+ if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) {
+ #nextshell diversion exists for this platform
+ write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname"
+
+ # $arguments = @($($MyInvocation.MyCommand.Path))
+ # $arguments += $args
+ # NOTE - this gives incorrect argument quoting e.g wrong number of arguments received by launched process for arguments: a "b c"
+ # $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait)
+ # Exit $process.ExitCode
+
+ & $nextshell_path $scriptname $args
+ exit $LASTEXITCODE
+ }
+}
+
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
+
+#
+
+#powershell -Command "Invoke-WebRequest -Uri 'https://www.gitea1.intx.com.au/jn/punkshell/raw/branch/master/getpunk.cmd' -OutFile 'getpunk.cmd'; Start-Process 'getpunk.cmd' -NoNewWindow -Wait"
+
+#todo - support either fossil or git
+
+#check if git available
+#if not, check/install winget, winget git
+
+$git_upstream = "https://gitea1.intx.com.au/jn/punkshell.git"
+$launchdir = Get-Location #store original CWD
+$scriptfolder = Resolve-Path (Split-Path -Path $PSCommandPath -Parent)
+$punkfolder = ""
+$scriptroot = "$([System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath))"
+$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+
+if (-not (Get-Command "git" -ErrorAction SilentlyContinue)) {
+ Write-Host "The git command doesn't seem to be available. Will attempt to install using winget."
+ #Find-Module/Install-Module: older mechanism, available in powershell
+ #Find-PSResource/Install-PSResource: only available in newer pwsh etc?
+ $wgclient = Get-Module -ListAvailable -Name Microsoft.WinGet.Client
+ if (${wgclient}.Length -eq 0) {
+ Write-Host "Microsoft.WinGet.Client module not installed.. will try to install."
+ Install-PackageProvider -Name NuGet -Force
+ $psgallery_existing_policy = (Get-PSRepository -Name PSGallery).InstallationPolicy
+ if ($psgallery_existing_policy -eq "Untrusted") {
+ #Applies to all versions of PowerShell for the user, and is persistent for current user.
+ #This has risks in that a powershell session started after this call, and before we reset it, will treat PSGallery as trusted
+ Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
+ }
+ Install-Module -Scope CurrentUser -Name Microsoft.Winget.Client -Force -Repository PSGallery
+ Repair-WinGetPackageManager
+ import-module -name Microsoft.Winget.client
+
+ if ($psgallery_existing_policy -eq "Untrusted") {
+ Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted
+ }
+ } else {
+ Write-Host "Microsoft.WinGet.Client is available"
+ }
+ $gitversion = (Find-WinGetPackage Git.Git).Version
+ if ($gitversion) {
+ Write-Host "Installing git version: ${gitversion}"
+ Install-WinGetPackage -Id "Git.Git"
+ } else {
+ Write-Host "Failed to find git using winget"
+ exit
+ }
+ #refreshing the current session's path should make the new command available (required for powershell 5 at least)
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+ if (Get-Command "git" -ErrorAction SilentlyContinue) {
+ Write-Host "git is now available"
+ } else {
+ Write-Host "git is still not available"
+ Write-HOst "Please install Git or relaunch your terminal and check it is available on the path."
+ exit
+ }
+}
+
+if (($launchdir.Path) -ne ($scriptfolder.Path)) {
+ Write-Host "The current directory does not seem to be the folder in which the getpunk script is located."
+ $answer = Read-Host "Do you want to use the current directory '$($launchdir.Path) as the location for punkshell? Y|N`n Y to use launchdir '$($launchdir.Path)'`n 'N' to use script folder '$($scriptfolder.Path)`n Any other value to abort: "
+ if ($answer -match "y") {
+ $punkfolder = $launchdir
+ } elseif ($answer -match "n") {
+ $punkfolder = $scriptfolder
+ } else {
+ exit 1
+ }
+
+} else {
+ $punkfolder = $scriptfolder
+}
+$punkfoldercontents = Get-ChildItem -Path $punkfolder -Force #include possibly hidden items such as .git folder
+$contentcount = ( $punkfoldercontents | Measure-Object).Count
+$effectively_empty = 0
+if ($contentcount -eq 0) {
+ $effectively_empty = 1
+} elseif ($punkfolder -eq $scriptfolder -and $contentcount -lt 10) {
+ #treat as empty if we have only a few files matching script root name
+ $scriptlike = get-childitem -Path $punkfolder | Where-Object {$_.name -like "${scriptroot}.*"}
+ if ($scriptlike.Count -eq $contentcount) {
+ $effectively_empty = 1
+ }
+}
+if (-not($effectively_empty)) {
+ if (-not(Test-Path -Path (Join-Path -Path $punkfolder -ChildPath ".git") -PathType Container)) {
+ Write-Host "The folder $punkfolder contains other items, and it does not appear to be a git project root."
+ Write-Host "Please place this script in an empty folder which is to be the punkshell base folder."
+ exit
+ } else {
+ $repo_origin = git remote get-url origin
+ if ($repo_origin -ne $git_upstream) {
+ Write-Host "The current repository origin '$repo_origin' is not the expected upstream '${git_upstream}'"
+ $answer = Read-Host "Continue anyway? (Y|N)"
+ if (-not($answer -match "y")) {
+ exit 1
+ }
+ }
+ }
+} else {
+ #punkfolder is empty, or has just the current script
+}
+Set-Location -Path $punkfolder
+
+
+if (-not(Test-Path -Path (Join-Path -Path $punkfolder -ChildPath ".git") -PathType Container)) {
+ git init
+ git remote add origin $git_upstream
+}
+git fetch origin
+if (($launchdir.Path) -eq ($scriptfolder.Path)) {
+ if (Test-Path -Path "${scriptroot}.cmd") {
+ #rename-item won't allow overwriting existing target file
+ Move-Item -Path "${scriptroot}.cmd" -Destination "${scriptroot}.cmd.lastrun" -Force
+ }
+}
+git pull $git_upstream master
+git checkout "${scriptroot}.cmd"
+git branch --set-upstream-to=origin/master master
+
+Set-Location $launchdir #restore original CWD
+#see also: https://github.com/jdhitsolutions/WTToolbox
+
+# Define the necessary Win32 API functions and constants
+Add-Type -TypeDefinition @"
+using System;
+using System.Runtime.InteropServices;
+
+public class WinAPI
+{
+ // Console Input/Output Handles
+ public const int STD_OUTPUT_HANDLE = -11;
+ public const uint ENABLE_QUICK_EDIT_MODE = 0x0040;
+ public const uint ENABLE_EXTENDED_FLAGS = 0x0080;
+ public const uint ENABLE_MOUSE_INPUT = 0x0010;
+ public const uint ENABLE_WINDOW_INPUT = 0x0008;
+ public const uint ENABLE_INSERT_MODE = 0x0020;
+ public const uint ENABLE_LINE_INPUT = 0x0002;
+ public const uint ENABLE_ECHO_INPUT = 0x0004;
+ public const uint ENABLE_PROCESSED_INPUT = 0x0001;
+
+ // Console Modes
+ public const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
+ public const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr GetStdHandle(int nStdHandle);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
+}
+"@
+
+# Get the handle to the console output buffer
+$stdoutHandle = [WinAPI]::GetStdHandle([WinAPI]::STD_OUTPUT_HANDLE)
+
+# Get the current console mode
+[uint32]$currentMode = 0
+if (![WinAPI]::GetConsoleMode($stdoutHandle, [ref]$currentMode)) {
+ Write-Error "Failed to get console mode. Error code: $($LAST_ERROR)"
+ return
+}
+
+# Enable virtual terminal processing
+$newMode = $currentMode -bor [WinAPI]::ENABLE_VIRTUAL_TERMINAL_PROCESSING
+
+# Set the new console mode
+if (-not [WinAPI]::SetConsoleMode($stdoutHandle, $newMode)) {
+ Write-Error "Failed to set console mode. Error code: $($LAST_ERROR)"
+ return
+}
+
+Write-Host "Virtual terminal processing enabled successfully."
+
+write-host "`e[92m getpunk done `e[m"
+
+#
+
+#
+#
+
+
+# -- --- --- --- --- --- --- ---
+#
+#tclsh $scriptname $args
+#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
+#
+# -- --- --- --- --- --- --- ---
+
+
+#
+#
+
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---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
+#
+
+#
+# -- unreachable by tcl directly if ctrl-z character is in the 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)
+#>
+
+
+
diff --git a/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd b/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
index 7bfca8d4..6642a26f 100644
--- a/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
+++ b/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
@@ -1,4 +1,4 @@
-: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh zsh/bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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 + \
@@ -13,7 +13,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: 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, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
+@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, zsh, bash, (sh diversion) 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 -------------------------------------------------------------------------------------------------------------------------------
@@ -847,16 +847,6 @@ namespace eval ::punk::multishell {
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#
-#
-#
-
-#
-#
-
-
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@@ -877,28 +867,61 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
-# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
-# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
-# zsh diversion \
-# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
-# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
-echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
-# non-bash-like diversion \
-if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
-# sh/bash (or zsh?) \
-shift && set -- "${@:1:$((${#@}-1))}"
-# \
-#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+echo "shell from ps: $ps_shellname"
# \
echo "args: $@"
+# ------------------------------------------------------------------------------
+# -- This if block wraps posix sh diversion section - only needed if Tcl didn't exit or return above.
+if false==false # else {
+ then
+ : #
+
+# https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c0 (MIT lic)
+# https://stackoverflow.com/questions/63864755/remove-last-argument-in-shell-script-posix
+# posix compliant pop
+pop() {
+ __pop_n=$(($1 - ${2:-1}))
+ if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
+ POP_EXPR='set -- "${@:1:'$__pop_n'}"'
+ elif [ $__pop_n -ge 500 ]; then
+ POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')"
+ else
+ __pop_index=0
+ __pop_arguments=""
+ while [ $__pop_index -lt $__pop_n ]; do
+ __pop_index=$((__pop_index+1))
+ __pop_arguments="$__pop_arguments \"\${$__pop_index}\""
+ done
+ POP_EXPR="set -- $__pop_arguments"
+ fi
+}
+# ------------------------------------------------------------------------------
+
+# non-bash-like posix diversion \
+if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
+ shift
+ pop $#
+ eval "$POP_EXPR"
+ echo "divert to bash $0 $@"
+
+ /usr/bin/env bash "$0" "$@"
+ exit $?
+fi
+# close false==false block
+fi
+# close tcl wrap }
# ------------------------------------------------------
-# -- This if block only needed if Tcl didn't exit or return above.
+# -- This if block wraps whole zsh/bash and perl sections - only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
+
+
+# zsh/bash \
+shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
@@ -910,7 +933,7 @@ if false==false # else {
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
-if [[ "$plat" = "Linux"* ]]; then
+if [[ "$plat" == "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
@@ -922,11 +945,11 @@ elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
-elif [[ "$plat" = "MINGW32"* ]]; then
+elif [[ "$plat" == "MINGW32"* ]]; then
os="win32"
-elif [[ "$plat" = "MINGW64"* ]]; then
+elif [[ "$plat" == "MINGW64"* ]]; then
os="win32"
-elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
@@ -972,62 +995,42 @@ for ln in "${arr_oslines[@]}"; do
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
- echo "nextshellpath: $nextshellpath"
+ # echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
- echo "nextshelltype: $nextshelltype"
+ # echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
- echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
- #echo "sh/bash reporting exitcode: ${exitcode}"
+ #echo "zsh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
- #echo "bash payload"
+ #echo "zsh/bash payload"
:
fi
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
-#printf "start of bash or sh code"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin zsh Payload
+#printf "start of bash or zsh code"
#
echo "No bash code for this script. Try another program such as perl or tcl" >&2
#
-#
-#
-
-# -- --- --- --- --- --- --- ---
-#
-#-- 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
-#
-# -- --- --- --- --- --- --- ---
-
-#
-#
-
-#printf "sh/bash done \n"
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
+#printf "zsh/bash done \n"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end zsh Payload
#------------------------------------------------------
fi
exit ${exitcode}
@@ -1063,7 +1066,6 @@ print "os $os\n";
# -- --- ---
-
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
@@ -1073,21 +1075,11 @@ foreach my $a(@ARGV) {
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#
-#
-#
-
-
-
# -- --- --- --- --- --- --- ---
-#
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
-#
# -- --- --- --- --- --- --- ---
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
@@ -1280,20 +1272,6 @@ Write-Error "No powershell code for this script. Try another program such as tcl
"powershell args : {0}" -f ($args -join ", ") | write-host
#
-#
-#
-
-
-# -- --- --- --- --- --- --- ---
-#
-#tclsh $scriptname $args
-#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
-#
-# -- --- --- --- --- --- --- ---
-
-
-#
-#
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE
diff --git a/src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd b/src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
index 7bfca8d4..6642a26f 100644
--- a/src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
+++ b/src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
@@ -1,4 +1,4 @@
-: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
+: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh zsh/bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported 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 + \
@@ -13,7 +13,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: 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, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
+@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, zsh, bash, (sh diversion) 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 -------------------------------------------------------------------------------------------------------------------------------
@@ -847,16 +847,6 @@ namespace eval ::punk::multishell {
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#
-#
-#
-
-#
-#
-
-
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@@ -877,28 +867,61 @@ if {[::punk::multishell::is_main]} {
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
-# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
-# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
-# zsh diversion \
-# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
-# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
-echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
-# non-bash-like diversion \
-if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
-# sh/bash (or zsh?) \
-shift && set -- "${@:1:$((${#@}-1))}"
-# \
-#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
+echo "shell from ps: $ps_shellname"
# \
echo "args: $@"
+# ------------------------------------------------------------------------------
+# -- This if block wraps posix sh diversion section - only needed if Tcl didn't exit or return above.
+if false==false # else {
+ then
+ : #
+
+# https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c0 (MIT lic)
+# https://stackoverflow.com/questions/63864755/remove-last-argument-in-shell-script-posix
+# posix compliant pop
+pop() {
+ __pop_n=$(($1 - ${2:-1}))
+ if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
+ POP_EXPR='set -- "${@:1:'$__pop_n'}"'
+ elif [ $__pop_n -ge 500 ]; then
+ POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')"
+ else
+ __pop_index=0
+ __pop_arguments=""
+ while [ $__pop_index -lt $__pop_n ]; do
+ __pop_index=$((__pop_index+1))
+ __pop_arguments="$__pop_arguments \"\${$__pop_index}\""
+ done
+ POP_EXPR="set -- $__pop_arguments"
+ fi
+}
+# ------------------------------------------------------------------------------
+
+# non-bash-like posix diversion \
+if [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ]; then
+ shift
+ pop $#
+ eval "$POP_EXPR"
+ echo "divert to bash $0 $@"
+
+ /usr/bin/env bash "$0" "$@"
+ exit $?
+fi
+# close false==false block
+fi
+# close tcl wrap }
# ------------------------------------------------------
-# -- This if block only needed if Tcl didn't exit or return above.
+# -- This if block wraps whole zsh/bash and perl sections - only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
+
+
+# zsh/bash \
+shift && set -- "${@:1:$((${#@}-1))}"
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
@@ -910,7 +933,7 @@ if false==false # else {
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
plat=$(uname -s) #platform/system
-if [[ "$plat" = "Linux"* ]]; then
+if [[ "$plat" == "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
@@ -922,11 +945,11 @@ elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
-elif [[ "$plat" = "MINGW32"* ]]; then
+elif [[ "$plat" == "MINGW32"* ]]; then
os="win32"
-elif [[ "$plat" = "MINGW64"* ]]; then
+elif [[ "$plat" == "MINGW64"* ]]; then
os="win32"
-elif [[ "$plat" = "CYGWIN_NT"* ]]; then
+elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
@@ -972,62 +995,42 @@ for ln in "${arr_oslines[@]}"; do
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
- echo "nextshellpath: $nextshellpath"
+ # echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
- echo "nextshelltype: $nextshelltype"
+ # echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
- echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
+ echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
- #echo "sh/bash reporting exitcode: ${exitcode}"
+ #echo "zsh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
- #echo "bash payload"
+ #echo "zsh/bash payload"
:
fi
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
-#printf "start of bash or sh code"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin zsh Payload
+#printf "start of bash or zsh code"
#
echo "No bash code for this script. Try another program such as perl or tcl" >&2
#
-#
-#
-
-# -- --- --- --- --- --- --- ---
-#
-#-- 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
-#
-# -- --- --- --- --- --- --- ---
-
-#
-#
-
-#printf "sh/bash done \n"
-# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
+#printf "zsh/bash done \n"
+# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end zsh Payload
#------------------------------------------------------
fi
exit ${exitcode}
@@ -1063,7 +1066,6 @@ print "os $os\n";
# -- --- ---
-
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
@@ -1073,21 +1075,11 @@ foreach my $a(@ARGV) {
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#
-#
-#
-
-
-
# -- --- --- --- --- --- --- ---
-#
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
-#
# -- --- --- --- --- --- --- ---
-#
-#
-
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
@@ -1280,20 +1272,6 @@ Write-Error "No powershell code for this script. Try another program such as tcl
"powershell args : {0}" -f ($args -join ", ") | write-host
#
-#
-#
-
-
-# -- --- --- --- --- --- --- ---
-#
-#tclsh $scriptname $args
-#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
-#
-# -- --- --- --- --- --- --- ---
-
-
-#
-#
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE