#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
@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).
@ -119,7 +119,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
# -- 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
# -- It is tuned to run (and possibly divert to different payload shell) when called from cmd.exe as a batch file, tclsh,sh,zsh,bash,perl or pwsh/powershell script
# -- i.e it is a polyglot file.
# -- The payload target (by os) is defined in the nextshell block at the top which is constructed when generating the polyglot
# -- using the tcl 'dev scriptwrap.multishell' command in a tcl punk shell
# -- The payload can be tcl,perl,powershell/pwsh or zsh/bash.
# -- 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
@ -720,7 +806,13 @@ do if not defined param1 set %%~"param1=%2%%~"
rename set ""; rename S set; set k {-- "$@""a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
if {[filesize$script_as_called]!=[filesize$script_ps1]}{
setneeds_updating 1
} else {
#both exist with same size - do full check that they're identical
catch {package require sha256}
if {[packageprovidesha256]ne""}{
seth1 [sha2::sha256 -hex -file $script_as_called]
seth2 [sha2::sha256 -hex -file $script_ps1]
if {[stringlength$h1]!=64||[stringlength$h2]!=64}{
setneeds_updating 1
} elseif {$h1 ne $h2} {
setneeds_updating 1
}
} else {
#manually compare - scripts aren't too big, so slurp and string compare is fine
setfd [open $script_as_called]
chan configure $fd -translation binary
setdata1 [read $fd]
close $fd
setfd [open $script_ps1]
chan configure $fd -translation binary
setdata2 [read $fd]
close $fd
if {![stringequal$data1$data2]}{
setneeds_updating 1
}
}
}
}
if {$needs_updating}{
file copy -force $script_as_called $script_ps1
}
} else {
#when called on the .ps1 - we assume it's up to date - review
}
setscrname $script_ps1
#set arglist [list]
#foreach a $::argv {
# set a "'$a'"
# lappend arglist $a
#}
} else {
setscrname [info script]
setscrname $script_as_called
setarglist $::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 {"}} {
setnextshell_words [list $nextshellpath]
} else {
setnextshell_words $nextshellpath
#review - test spaced quoted words in nextshellpath?
#
#if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
# set nextshell_words [list $nextshellpath]
#} else {
# set nextshell_words $nextshellpath
#}
#perform any msys argument munging on a cmd/cmd.exe based nextshellpath before we convert the first word to an auto_exec path
switch -glob -- $plat {
"msys" - "mingw*" {
setcmdword [lindex $nextshellpath 0]
#we only act on cmd or cmd.exe - not a full path such as c:/WINDOWS/system32/cmd.exe
#the nextshellpath should generally be configured as cmd /c ... or cmd.exe ... but specifying it as a path could allow bypassing this un-munging.
#The un-munging only applies to msys/mingw, so such bypassing should be unnecessary - review
#maint: keep this munging in sync with zsh/bash and perl blocks which must also do msys mangling
if {[regexp{^cmd$|^cmd[.]exe$}$cmdword]}{
#need to deal with msys argument munging
puts stderr "cmd call via msys detected. performing translation of /c to //C"
#for now we only deal with /C or /c - todo - other cmd.exe flags?
#In this context we would usually only be using cmd.exe /c to launch older 'desktop' powershell to avoid spaced-argument problems - so we aren't expecting other flags
setnew_nextshellpath [list $cmdword]
#for now - just do what zsh munging does - bash regex/string/array processing is tedious and footgunny for the unfamiliar (me),
#so determine the minimum viable case for code there, then port behaviour to perl/tcl msys munging sections.
foreach w [lrange $nextshellpath 1 end] {
if {[regexp{^/[Cc]$}$w]}{
lappend new_nextshellpath {//C}
} else {
lappend new_nextshellpath $w
}
}
setnextshellpath $new_nextshellpath
}
}
}
setns_firstword [lindex $nextshellpath 0]
if {[stringindex$ns_firstword0]eq{"}&&[stringindex$ns_firstwordend]eq{"}}{
setns_firstword [string range $ns_firstword 1 end-1]
}
#review - is this test for extra layer of double quoting on first word really necessary?
#if we are treaing $nextshellpath as a tcl list - the first layer of double quotes will already have disappeared
##if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
## set ns_firstword [string range $ns_firstword 1 end-1]
##}
if {[stringmatch{/*/env}$ns_firstword]&&$::tcl_platform(platform)ne"windows"}{
if {$::tcl_platform(platform)ne"windows"&&[stringmatch{/*/env}$ns_firstword]}{
setexec_part $nextshellpath
} else {
setepath [auto_execok $ns_firstword]
@ -794,6 +975,10 @@ if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
IFS=$' \t\n'
# review
fi
nextshellpath=""
nextshelltype=""
@ -1008,8 +1206,56 @@ exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [["$nextshelltype"!="bash"&&"$nextshelltype"!="none"]];then
echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0""$@"
${nextshellpath} "$0""$@"
script="$0"
if [["$nextshelltype"=="pwsh"||"$nextshelltype" == "powershell" ]]; then
#powershell requires the file extension to be .ps1 (always - on windows)
#on other platforms it's not required if a shebang line is used - but this script must be shebangless for portability and to maintain polyglot capabilities.
cmdpattern="[.]cmd$"
if [["$script"=~$cmdpattern]];then
ps1script="${script%????}.ps1"
if !cmp-s"$script""$ps1script";then
#ps1script either different or missing
#on windows - batch script copies .cmd -> .ps1 if not identical
cp -f "$script""$ps1script"
fi
script=$ps1script
fi
fi
if [["$plat"=="MSYS_NT"* ]]; then
#we need to deal with MSYS argument munging
cmdpattern="^cmd.exe |^cmd "
#do not double quote cmdpattern - or it will be treated as literal string
if [["$nextshellpath"=~$cmdpattern]];then
#for now - tell the user what's going on
echo"cmd call via msys detected. performing translation of /c to //c and escaping backslashes in script path">&2
#flags to cmd.exe such as /c are interpreted by msys as looking like a unix path
#review - for nextshellpath targets specified in the block for win32 - we don't expect unix paths (?)
#what about other flags? - can we just double up all forward slashes?
#maint: keep this munging in sync with the tcl block and perl block which must also do msys munging
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
#.. but it is impossible to modify or reassign the automatic $args variable
# so let's start yet another whole new process just to remove one leading argument so the custom script can operate on parameters cleanly - thanks powershell :/
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
@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).
@ -119,7 +119,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
# -- 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
# -- It is tuned to run (and possibly divert to different payload shell) when called from cmd.exe as a batch file, tclsh,sh,zsh,bash,perl or pwsh/powershell script
# -- i.e it is a polyglot file.
# -- The payload target (by os) is defined in the nextshell block at the top which is constructed when generating the polyglot
# -- using the tcl 'dev scriptwrap.multishell' command in a tcl punk shell
# -- The payload can be tcl,perl,powershell/pwsh or zsh/bash.
# -- 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
@ -720,7 +806,13 @@ do if not defined param1 set %%~"param1=%2%%~"
rename set ""; rename S set; set k {-- "$@""a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
if {[filesize$script_as_called]!=[filesize$script_ps1]}{
setneeds_updating 1
} else {
#both exist with same size - do full check that they're identical
catch {package require sha256}
if {[packageprovidesha256]ne""}{
seth1 [sha2::sha256 -hex -file $script_as_called]
seth2 [sha2::sha256 -hex -file $script_ps1]
if {[stringlength$h1]!=64||[stringlength$h2]!=64}{
setneeds_updating 1
} elseif {$h1 ne $h2} {
setneeds_updating 1
}
} else {
#manually compare - scripts aren't too big, so slurp and string compare is fine
setfd [open $script_as_called]
chan configure $fd -translation binary
setdata1 [read $fd]
close $fd
setfd [open $script_ps1]
chan configure $fd -translation binary
setdata2 [read $fd]
close $fd
if {![stringequal$data1$data2]}{
setneeds_updating 1
}
}
}
}
if {$needs_updating}{
file copy -force $script_as_called $script_ps1
}
} else {
#when called on the .ps1 - we assume it's up to date - review
}
setscrname $script_ps1
#set arglist [list]
#foreach a $::argv {
# set a "'$a'"
# lappend arglist $a
#}
} else {
setscrname [info script]
setscrname $script_as_called
setarglist $::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 {"}} {
setnextshell_words [list $nextshellpath]
} else {
setnextshell_words $nextshellpath
#review - test spaced quoted words in nextshellpath?
#
#if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
# set nextshell_words [list $nextshellpath]
#} else {
# set nextshell_words $nextshellpath
#}
#perform any msys argument munging on a cmd/cmd.exe based nextshellpath before we convert the first word to an auto_exec path
switch -glob -- $plat {
"msys" - "mingw*" {
setcmdword [lindex $nextshellpath 0]
#we only act on cmd or cmd.exe - not a full path such as c:/WINDOWS/system32/cmd.exe
#the nextshellpath should generally be configured as cmd /c ... or cmd.exe ... but specifying it as a path could allow bypassing this un-munging.
#The un-munging only applies to msys/mingw, so such bypassing should be unnecessary - review
#maint: keep this munging in sync with zsh/bash and perl blocks which must also do msys mangling
if {[regexp{^cmd$|^cmd[.]exe$}$cmdword]}{
#need to deal with msys argument munging
#for now we only deal with /C or /c - todo - other cmd.exe flags?
#In this context we would usually only be using cmd.exe /c to launch older 'desktop' powershell to avoid spaced-argument problems - so we aren't expecting other flags
setnew_nextshellpath [list $cmdword]
#for now - just do what zsh munging does - bash regex/string/array processing is tedious and footgunny for the unfamiliar (me),
#so determine the minimum viable case for code there, then port behaviour to perl/tcl msys munging sections.
foreach w [lrange $nextshellpath 1 end] {
if {[regexp{^/[Cc]$}$w]}{
lappend new_nextshellpath {//C}
} else {
lappend new_nextshellpath $w
}
}
setnextshellpath $new_nextshellpath
}
}
}
setns_firstword [lindex $nextshellpath 0]
if {[stringindex$ns_firstword0]eq{"}&&[stringindex$ns_firstwordend]eq{"}}{
setns_firstword [string range $ns_firstword 1 end-1]
}
#review - is this test for extra layer of double quoting on first word really necessary?
#if we are treaing $nextshellpath as a tcl list - the first layer of double quotes will already have disappeared
##if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
## set ns_firstword [string range $ns_firstword 1 end-1]
##}
if {[stringmatch{/*/env}$ns_firstword]&&$::tcl_platform(platform)ne"windows"}{
if {$::tcl_platform(platform)ne"windows"&&[stringmatch{/*/env}$ns_firstword]}{
setexec_part $nextshellpath
} else {
setepath [auto_execok $ns_firstword]
@ -794,6 +974,10 @@ if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
IFS=$' \t\n'
# review
fi
nextshellpath=""
nextshelltype=""
@ -1008,8 +1205,55 @@ exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [["$nextshelltype"!="bash"&&"$nextshelltype"!="none"]];then
echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0""$@"
${nextshellpath} "$0""$@"
script="$0"
if [["$nextshelltype"=="pwsh"||"$nextshelltype" == "powershell" ]]; then
#powershell requires the file extension to be .ps1 (always - on windows)
#on other platforms it's not required if a shebang line is used - but this script must be shebangless for portability and to maintain polyglot capabilities.
cmdpattern="[.]cmd$"
if [["$script"=~$cmdpattern]];then
ps1script="${script%????}.ps1"
if !cmp-s"$script""$ps1script";then
#ps1script either different or missing
#on windows - batch script copies .cmd -> .ps1 if not identical
cp -f "$script""$ps1script"
fi
script=$ps1script
fi
fi
if [["$plat"=="MSYS_NT"* ]]; then
#we need to deal with MSYS argument munging
cmdpattern="^cmd.exe |^cmd "
#do not double quote cmdpattern - or it will be treated as literal string
if [["$nextshellpath"=~$cmdpattern]];then
#for now - tell the user what's going on
echo"cmd call via msys detected. performing translation of /c to //c and escaping backslashes in script path"
#flags to cmd.exe such as /c are interpreted by msys as looking like a unix path
#review - for nextshellpath targets specified in the block for win32 - we don't expect unix paths (?)
#what about other flags? - can we just double up all forward slashes?
#maint: keep this munging in sync with the tcl block and perl block which must also do msys munging
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
#.. but it is impossible to modify or reassign the automatic $args variable
# so let's start yet another whole new process just to remove one leading argument so the custom script can operate on parameters cleanly - thanks powershell :/